/*-------------------------------------------------------------------------
 *
 * prepjointree.c
 *	  计划对子查询和联接树的预处理。
 *
 * 注意：调用这些操作的预定顺序是
 *		replace_empty_jointree
 *		pull_up_sublinks
 *		preprocess_function_rtes
 *		pull_up_subqueries
 *		flatten_simple_union_all
 *		do expression preprocessing (包括扁平化 JOIN 别名变量)
 *		reduce_outer_joins
 *		remove_useless_result_rtes
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/optimizer/prep/prepjointree.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "catalog/pg_type.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/optimizer.h"
#include "optimizer/placeholder.h"
#include "optimizer/prep.h"
#include "optimizer/subselect.h"
#include "optimizer/tlist.h"
#include "parser/parse_relation.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"


typedef struct pullup_replace_vars_context
{
	PlannerInfo *root;
	List	   *targetlist;		/* 正在提取的子查询的 tlist */
	RangeTblEntry *target_rte;	/* 子查询的 RTE */
	Relids		relids;			/* 子查询内的 relids，按
								 * 提取后的编号（仅在 target_rte->lateral 时设置） */
	bool	   *outer_hasSubLinks;	/* -> 外部查询的 hasSubLinks */
	int			varno;			/* 子查询的 varno */
	bool		need_phvs;		/* 我们需要 PlaceHolderVars 吗？ */
	bool		wrap_non_vars;	/* 我们需要在 *所有* 非变量上使用它们吗？ */
	Node	  **rv_cache;		/* 带有 PHVs 的结果缓存 */
} pullup_replace_vars_context;

typedef struct reduce_outer_joins_state
{
	Relids		relids;			/* 此子树内的基本 relids */
	bool		contains_outer; /* 子树中是否包含外连接？ */
	List	   *sub_states;		/* 子树组件的状态列表 */
} reduce_outer_joins_state;

static Node *fc_pull_up_sublinks_jointree_recurse(PlannerInfo *fc_root, Node *fc_jtnode,
											   Relids *fc_relids);
static Node *fc_pull_up_sublinks_qual_recurse(PlannerInfo *fc_root, Node *fc_node,
										   Node **fc_jtlink1, Relids fc_available_rels1,
										   Node **fc_jtlink2, Relids fc_available_rels2);
static Node *fc_pull_up_subqueries_recurse(PlannerInfo *fc_root, Node *fc_jtnode,
										JoinExpr *fc_lowest_outer_join,
										JoinExpr *fc_lowest_nulling_outer_join,
										AppendRelInfo *fc_containing_appendrel);
static Node *fc_pull_up_simple_subquery(PlannerInfo *fc_root, Node *fc_jtnode,
									 RangeTblEntry *fc_rte,
									 JoinExpr *fc_lowest_outer_join,
									 JoinExpr *fc_lowest_nulling_outer_join,
									 AppendRelInfo *fc_containing_appendrel);
static Node *fc_pull_up_simple_union_all(PlannerInfo *fc_root, Node *fc_jtnode,
									  RangeTblEntry *fc_rte);
static void fc_pull_up_union_leaf_queries(Node *fc_setOp, PlannerInfo *fc_root,
									   int fc_parentRTindex, Query *fc_setOpQuery,
									   int fc_childRToffset);
static void fc_make_setop_translation_list(Query *fc_query, int fc_newvarno,
										AppendRelInfo *fc_appinfo);
static bool fc_is_simple_subquery(PlannerInfo *fc_root, Query *fc_subquery,
							   RangeTblEntry *fc_rte,
							   JoinExpr *fc_lowest_outer_join);
static Node *fc_pull_up_simple_values(PlannerInfo *fc_root, Node *fc_jtnode,
								   RangeTblEntry *fc_rte);
static bool fc_is_simple_values(PlannerInfo *fc_root, RangeTblEntry *fc_rte);
static Node *fc_pull_up_constant_function(PlannerInfo *fc_root, Node *fc_jtnode,
									   RangeTblEntry *fc_rte,
									   JoinExpr *fc_lowest_nulling_outer_join,
									   AppendRelInfo *fc_containing_appendrel);
static bool fc_is_simple_union_all(Query *fc_subquery);
static bool fc_is_simple_union_all_recurse(Node *fc_setOp, Query *fc_setOpQuery,
										List *fc_colTypes);
static bool fc_is_safe_append_member(Query *fc_subquery);
static bool fc_jointree_contains_lateral_outer_refs(PlannerInfo *fc_root,
												 Node *fc_jtnode, bool fc_restricted,
												 Relids fc_safe_upper_varnos);
static void fc_perform_pullup_replace_vars(PlannerInfo *fc_root,
										pullup_replace_vars_context *fc_rvcontext,
										JoinExpr *fc_lowest_nulling_outer_join,
										AppendRelInfo *fc_containing_appendrel);
static void fc_replace_vars_in_jointree(Node *fc_jtnode,
									 pullup_replace_vars_context *fc_context,
									 JoinExpr *fc_lowest_nulling_outer_join);
static Node *fc_pullup_replace_vars(Node *fc_expr,
								 pullup_replace_vars_context *fc_context);
static Node *fc_pullup_replace_vars_callback(Var *fc_var,
										  replace_rte_variables_context *fc_context);
static Query *fc_pullup_replace_vars_subquery(Query *fc_query,
										   pullup_replace_vars_context *fc_context);
static reduce_outer_joins_state *fc_reduce_outer_joins_pass1(Node *fc_jtnode);
static void fc_reduce_outer_joins_pass2(Node *fc_jtnode,
									 reduce_outer_joins_state *fc_state,
									 PlannerInfo *fc_root,
									 Relids fc_nonnullable_rels,
									 List *fc_nonnullable_vars,
									 List *fc_forced_null_vars);
static Node *fc_remove_useless_results_recurse(PlannerInfo *fc_root, Node *fc_jtnode);
static int	get_result_relid(PlannerInfo *fc_root, Node *fc_jtnode);
static void fc_remove_result_refs(PlannerInfo *fc_root, int fc_varno, Node *fc_newjtloc);
static bool fc_find_dependent_phvs(PlannerInfo *fc_root, int fc_varno);
static bool fc_find_dependent_phvs_in_jointree(PlannerInfo *fc_root,
											Node *fc_node, int fc_varno);
static void fc_substitute_phv_relids(Node *fc_node,
								  int fc_varno, Relids fc_subrelids);
static void fc_fix_append_rel_relids(List *fc_append_rel_list, int fc_varno,
								  Relids fc_subrelids);
static Node *fc_find_jointree_node_for_rel(Node *fc_jtnode, int fc_relid);


/*
 * transform_MERGE_to_join
 *		替换 MERGE 的联合树以包含目标关系。
 */
void transform_MERGE_to_join(Query *fc_parse)
{
	RangeTblEntry *fc_joinrte;
	JoinExpr   *fc_joinexpr;
	JoinType	fc_jointype;
	int			fc_joinrti;
	List	   *fc_vars;

	if (fc_parse->commandType != CMD_MERGE)
		return;

	/* XXX 可能无效 */
	fc_vars = NIL;

	/*
	 * 当存在任何 WHEN NOT MATCHED THEN INSERT 子句时，我们需要使用外连接
	 * 以处理源关系中所有未匹配的元组。如果不存在，我们可以使用内连接。
	 */
	if (fc_parse->mergeUseOuterJoin)
		fc_jointype = JOIN_RIGHT;
	else
		fc_jointype = JOIN_INNER;

	/* 制造一个用于使用的 join RTE。 */
	fc_joinrte = makeNode(RangeTblEntry);
	fc_joinrte->rtekind = RTE_JOIN;
	fc_joinrte->jointype = fc_jointype;
	fc_joinrte->joinmergedcols = 0;
	fc_joinrte->joinaliasvars = fc_vars;
	fc_joinrte->joinleftcols = NIL;	/* MERGE 不允许使用 JOIN USING */
	fc_joinrte->joinrightcols = NIL;	/* 同上 */
	fc_joinrte->join_using_alias = NULL;

	fc_joinrte->alias = NULL;
	fc_joinrte->eref = makeAlias("*MERGE*", NIL);
	fc_joinrte->lateral = false;
	fc_joinrte->inh = false;
	fc_joinrte->inFromCl = true;
	fc_joinrte->requiredPerms = 0;
	fc_joinrte->checkAsUser = InvalidOid;
	fc_joinrte->selectedCols = NULL;
	fc_joinrte->insertedCols = NULL;
	fc_joinrte->updatedCols = NULL;
	fc_joinrte->extraUpdatedCols = NULL;
	fc_joinrte->securityQuals = NIL;

	/*
	 * 将完成的 RTE 添加到 pstate 的范围表列表中，以便我们知道它的
	 * 索引。
	 */
	fc_parse->rtable = lappend(fc_parse->rtable, fc_joinrte);
	fc_joinrti = list_length(fc_parse->rtable);

	/*
	 * 在目标和源关系之间创建一个 JOIN。
	 */
	fc_joinexpr = makeNode(JoinExpr);
	fc_joinexpr->jointype = fc_jointype;
	fc_joinexpr->isNatural = false;
	fc_joinexpr->larg = (Node *) makeNode(RangeTblRef);
	((RangeTblRef *) fc_joinexpr->larg)->rtindex = fc_parse->resultRelation;
	fc_joinexpr->rarg = linitial(fc_parse->jointree->fromlist);	/* 原始连接 */
	fc_joinexpr->usingClause = NIL;
	fc_joinexpr->join_using_alias = NULL;
	/* 资格从jointree中移除，并放入这个特定的连接中 */
	fc_joinexpr->quals = fc_parse->jointree->quals;
	fc_joinexpr->alias = NULL;
	fc_joinexpr->rtindex = fc_joinrti;

	/* 让新的连接成为查询的联合树中的唯一条目 */
	fc_parse->jointree->fromlist = list_make1(fc_joinexpr);
	fc_parse->jointree->quals = NULL;
}

/*
 * replace_empty_jointree
 *		如果查询的联合树为空，则用伪 RTE_RESULT
 *		关系替换它。
 *
 * 这样做，可以避免以前在省略 FROM 子句的 SELECT 中存在的一些边界情况。
 * 一个例子是，空联合树的子查询之前无法提取，因为这样会导致空 relid 集合，
 * 使得子查询在连接或 PlaceHolderVar 处理时无法唯一识别。
 *
 * 与此文件中的大多数其他函数不同，该函数不进行递归；
 * 我们依赖其他处理在合适的时机在子查询上调用它。
 */
void replace_empty_jointree(Query *fc_parse)
{
	RangeTblEntry *fc_rte;
	Index		fc_rti;
	RangeTblRef *fc_rtr;

	/* 如果联合树已非空，则无需操作 */
	if (fc_parse->jointree->fromlist != NIL)
		return;

	/* 我们也不能在集合操作树的顶层更改它 */
	if (fc_parse->setOperations)
		return;

	/* 创建适当的 RTE */
	fc_rte = makeNode(RangeTblEntry);
	fc_rte->rtekind = RTE_RESULT;
	fc_rte->eref = makeAlias("*RESULT*", NIL);

	/* 将其添加到 rangetable */
	fc_parse->rtable = lappend(fc_parse->rtable, fc_rte);
	fc_rti = list_length(fc_parse->rtable);

	/* 并在联合树中插入引用 */
	fc_rtr = makeNode(RangeTblRef);
	fc_rtr->rtindex = fc_rti;
	fc_parse->jointree->fromlist = list_make1(fc_rtr);
}

/*
 * pull_up_sublinks
 *		尝试将 ANY 和 EXISTS 子链接提取为
 *		半连接或反半连接。
 *
 * 子句 "foo op ANY (sub-SELECT)" 可以通过提取
 * 子查询成为 rangetable 条目，并将隐含的比较视为半连接的资格进行处理。
 * 然而，此优化 *仅* 在 WHERE 或 JOIN/ON 子句的顶层有效，
 * 因为我们无法区分在涉及 NULL 输入的情况下 ANY 应该返回 FALSE 还是 NULL。
 * 此外，在外连接的 ON 子句中，只有在子链接是退化的（即，仅引用连接的可空侧）时，
 * 我们才能做到这一点。在这种情况下，将半连接下推到连接的可空侧是合法的。
 * 如果子链接引用了任何非可空侧变量，则必须在外连接中进行评估，
 * 这会使事情变得复杂得多。
 *
 * 在类似条件下，可以通过提取子查询并创建半连接或反半连接处理 EXISTS 和 NOT EXISTS 子句。
 *
 * 这个例程会搜索这些子句，并在发现任何内容时执行必要的解析树转换。
 *
 * 这个例程必须在 preprocess_expression() 之前运行，因此资格
 * 子句尚未简化为隐式和格式，也无法保证是 AND/OR 扁平的。
 * 这意味着我们需要递归搜索显式 AND 子句。一旦遇到非 AND 项，我们就停止。
 */
void pull_up_sublinks(PlannerInfo *fc_root)
{
	Node	   *fc_jtnode;
	Relids		fc_relids;

	/* 开始通过联合树进行递归 */
	fc_jtnode = fc_pull_up_sublinks_jointree_recurse(fc_root,
											   (Node *) fc_root->parse->jointree,
											   &fc_relids);

	/*
	 * root->parse->jointree 必须始终是 FromExpr，因此如果我们
	 * 从递归中得到的是裸 RangeTblRef 或 JoinExpr，则插入一个伪造的。
	 */
	if (IsA(fc_jtnode, FromExpr))
		fc_root->parse->jointree = (FromExpr *) fc_jtnode;
	else
		fc_root->parse->jointree = makeFromExpr(list_make1(fc_jtnode), NULL);
}

/*
 * 递归通过联合树节点处理 pull_up_sublinks()
 *
 * 除了返回可能已修改的联合树节点外，我们还将包含关系返回到 *relids 中。
 */
static Node * fc_pull_up_sublinks_jointree_recurse(PlannerInfo *fc_root, Node *fc_jtnode,
								  Relids *fc_relids)
{
	/* 由于此函数会递归，可能会导致堆栈溢出。 */
	check_stack_depth();

	if (fc_jtnode == NULL)
	{
		*fc_relids = NULL;
	}
	else if (IsA(fc_jtnode, RangeTblRef))
	{
		int			fc_varno = ((RangeTblRef *) fc_jtnode)->rtindex;

		*fc_relids = bms_make_singleton(fc_varno);
		/* jtnode 未被修改返回 */
	}
	else if (IsA(fc_jtnode, FromExpr))
	{
		FromExpr   *fc_f = (FromExpr *) fc_jtnode;
		List	   *fc_newfromlist = NIL;
		Relids		fc_frelids = NULL;
		FromExpr   *fc_newf;
		Node	   *fc_jtlink;
		ListCell   *fc_l;

		/* 首先，递归处理子项并收集其 relids */
		foreach(fc_l, fc_f->fromlist)
		{
			Node	   *fc_newchild;
			Relids		fc_childrelids;

			fc_newchild = fc_pull_up_sublinks_jointree_recurse(fc_root,
														 lfirst(fc_l),
														 &fc_childrelids);
			fc_newfromlist = lappend(fc_newfromlist, fc_newchild);
			fc_frelids = bms_join(fc_frelids, fc_childrelids);
		}
		/* 构建替换的 FromExpr；目前没有资格 */
		fc_newf = makeFromExpr(fc_newfromlist, NULL);
		/* 设置一个表示重建联合树的链接 */
		fc_jtlink = (Node *) fc_newf;
		/* 现在处理资格——所有子项均可用 */
		fc_newf->quals = fc_pull_up_sublinks_qual_recurse(fc_root, fc_f->quals,
													&fc_jtlink, fc_frelids,
													NULL, NULL);

		/*
		 * 请注意，结果将是 newf，或者是具有 newf 基部的 JoinExprs 堆栈。
		 * 我们依赖后续的优化步骤将其扁平和重新安排连接。
		 *
		 * 尽管我们可以将提取的子查询包含在返回的 relids 中，
		 * 但没有必要，因为上层资格都无法引用其输出。
		 */
		*fc_relids = fc_frelids;
		fc_jtnode = fc_jtlink;
	}
	else if (IsA(fc_jtnode, JoinExpr))
	{
		JoinExpr   *fc_j;
		Relids		fc_leftrelids;
		Relids		fc_rightrelids;
		Node	   *fc_jtlink;

		/*
		 * 制作连接节点的可修改副本，但不要急于复制其
		 * 子节点（尚未）。
		 */
		fc_j = (JoinExpr *) palloc(sizeof(JoinExpr));
		memcpy(fc_j, fc_jtnode, sizeof(JoinExpr));
		fc_jtlink = (Node *) fc_j;

		/* 递归处理子项并收集其 relids */
		fc_j->larg = fc_pull_up_sublinks_jointree_recurse(fc_root, fc_j->larg,
													&fc_leftrelids);
		fc_j->rarg = fc_pull_up_sublinks_jointree_recurse(fc_root, fc_j->rarg,
													&fc_rightrelids);

		/*
		 * 现在处理资格，显示适当的子项 relids 作为可用，
		 * 并在适当的位置附加任何提取的联合树项。在内连接案例中，
		 * 我们将新的 JoinExpr 放在现有连接的上方（就像
		 * FromExpr 风格的连接）。在外连接的情况下，新的
		 * JoinExpr 必须放入外连接的可空侧。可用关系的操作
		 * 保证我们只为允许情况提取资格。
		 *
		 * 我们不期望在这里看到任何预先存在的 JOIN_SEMI 或 JOIN_ANTI
		 * 节点。
		 */
		switch (fc_j->jointype)
		{
			case JOIN_INNER:
				fc_j->quals = fc_pull_up_sublinks_qual_recurse(fc_root, fc_j->quals,
														 &fc_jtlink,
														 bms_union(fc_leftrelids,
																   fc_rightrelids),
														 NULL, NULL);
				break;
			case JOIN_LEFT:
				fc_j->quals = fc_pull_up_sublinks_qual_recurse(fc_root, fc_j->quals,
														 &fc_j->rarg,
														 fc_rightrelids,
														 NULL, NULL);
				break;
			case JOIN_FULL:
				/* 无法处理全连接资格 */
				break;
			case JOIN_RIGHT:
				fc_j->quals = fc_pull_up_sublinks_qual_recurse(fc_root, fc_j->quals,
														 &fc_j->larg,
														 fc_leftrelids,
														 NULL, NULL);
				break;
			default:
				elog(ERROR, "unrecognized join type: %d",
					 (int) fc_j->jointype);
				break;
		}

		/*
		 * 尽管我们可以将提取的子查询包含在返回的 relids 中，
		 * 但没有必要，因为上层资格都无法引用其输出。 但是我们
		 * *确实* 需要包含连接自身的 rtindex，因为我们尚未
		 * 合并连接别名变量，因此上层会错误地认为它们无法使用对此
		 * 连接的引用。
		 */
		*fc_relids = bms_join(fc_leftrelids, fc_rightrelids);
		if (fc_j->rtindex)
			*fc_relids = bms_add_member(*fc_relids, fc_j->rtindex);
		fc_jtnode = fc_jtlink;
	}
	else
		elog(ERROR, "unrecognized node type: %d",
			 (int) nodeTag(fc_jtnode));
	return fc_jtnode;
}


/*
 * 递归遍历顶级条件节点以进行 pull_up_sublinks()
 *
 * jtlink1 指向连接树中应插入任何新 JoinExprs 的链接，
 * 如果它们引用 available_rels1（即，available_rels1 表示在 jtlink1 下的关系）。
 * 可选地，jtlink2 可以指向第二个链接，在引用 available_rels2 的情况下
 * 应插入新 JoinExprs（如果未使用这两个参数，则传递 NULL）。
 * 请注意，引用两组变量的 SubLinks 不能被优化。
 * 如果我们找到多个可以提升的 SubLinks，它们将按照我们遇到的顺序
 * 堆叠到 jtlink1 和/或 jtlink2 上。
 * 我们依赖后续的优化来在适当的情况下重新排列堆栈。
 *
 * 返回替代的条件节点，或返回 NULL 如果条件应被移除。
 */
static Node * fc_pull_up_sublinks_qual_recurse(PlannerInfo *fc_root, Node *fc_node,
							  Node **fc_jtlink1, Relids fc_available_rels1,
							  Node **fc_jtlink2, Relids fc_available_rels2)
{
	if (fc_node == NULL)
		return NULL;
	if (IsA(fc_node, SubLink))
	{
		SubLink    *fc_sublink = (SubLink *) fc_node;
		JoinExpr   *fc_j;
		Relids		fc_child_rels;

		/* 这是可转换的 ANY 或 EXISTS 子句吗？ */
		if (fc_sublink->subLinkType == ANY_SUBLINK)
		{
			if ((fc_j = convert_ANY_sublink_to_join(fc_root, fc_sublink,
												 fc_available_rels1)) != NULL)
			{
				/* 是的；将新连接节点插入连接树 */
				fc_j->larg = *fc_jtlink1;
				*fc_jtlink1 = (Node *) fc_j;
				/* 递归处理提升的连接树节点 */
				fc_j->rarg = fc_pull_up_sublinks_jointree_recurse(fc_root,
															fc_j->rarg,
															&fc_child_rels);

				/*
				 * 现在递归处理提升的条件。任何插入的
				 * 连接可以堆叠到 j->larg 或 j->rarg，
				 * 具体取决于它们引用的关系。
				 */
				fc_j->quals = fc_pull_up_sublinks_qual_recurse(fc_root,
														 fc_j->quals,
														 &fc_j->larg,
														 fc_available_rels1,
														 &fc_j->rarg,
														 fc_child_rels);
				/* 返回 NULL 代表恒定 TRUE */
				return NULL;
			}
			if (fc_available_rels2 != NULL &&
				(fc_j = convert_ANY_sublink_to_join(fc_root, fc_sublink,
												 fc_available_rels2)) != NULL)
			{
				/* 是的；将新连接节点插入连接树 */
				fc_j->larg = *fc_jtlink2;
				*fc_jtlink2 = (Node *) fc_j;
				/* 递归处理提升的连接树节点 */
				fc_j->rarg = fc_pull_up_sublinks_jointree_recurse(fc_root,
															fc_j->rarg,
															&fc_child_rels);

				/*
				 * 现在递归处理提升的条件。任何插入的
				 * 连接可以堆叠到 j->larg 或 j->rarg，
				 * 具体取决于它们引用的关系。
				 */
				fc_j->quals = fc_pull_up_sublinks_qual_recurse(fc_root,
														 fc_j->quals,
														 &fc_j->larg,
														 fc_available_rels2,
														 &fc_j->rarg,
														 fc_child_rels);
				/* 返回 NULL 代表恒定 TRUE */
				return NULL;
			}
		}
		else if (fc_sublink->subLinkType == EXISTS_SUBLINK)
		{
			if ((fc_j = convert_EXISTS_sublink_to_join(fc_root, fc_sublink, false,
													fc_available_rels1)) != NULL)
			{
				/* 是的；将新连接节点插入连接树 */
				fc_j->larg = *fc_jtlink1;
				*fc_jtlink1 = (Node *) fc_j;
				/* 递归处理提升的连接树节点 */
				fc_j->rarg = fc_pull_up_sublinks_jointree_recurse(fc_root,
															fc_j->rarg,
															&fc_child_rels);

				/*
				 * 现在递归处理提升的条件。任何插入的
				 * 连接可以堆叠到 j->larg 或 j->rarg，
				 * 具体取决于它们引用的关系。
				 */
				fc_j->quals = fc_pull_up_sublinks_qual_recurse(fc_root,
														 fc_j->quals,
														 &fc_j->larg,
														 fc_available_rels1,
														 &fc_j->rarg,
														 fc_child_rels);
				/* 返回 NULL 代表恒定 TRUE */
				return NULL;
			}
			if (fc_available_rels2 != NULL &&
				(fc_j = convert_EXISTS_sublink_to_join(fc_root, fc_sublink, false,
													fc_available_rels2)) != NULL)
			{
				/* 是的；将新连接节点插入连接树 */
				fc_j->larg = *fc_jtlink2;
				*fc_jtlink2 = (Node *) fc_j;
				/* 递归处理提升的连接树节点 */
				fc_j->rarg = fc_pull_up_sublinks_jointree_recurse(fc_root,
															fc_j->rarg,
															&fc_child_rels);

				/*
				 * 现在递归处理提升的条件。任何插入的
				 * 连接可以堆叠到 j->larg 或 j->rarg，
				 * 具体取决于它们引用的关系。
				 */
				fc_j->quals = fc_pull_up_sublinks_qual_recurse(fc_root,
														 fc_j->quals,
														 &fc_j->larg,
														 fc_available_rels2,
														 &fc_j->rarg,
														 fc_child_rels);
				/* 返回 NULL 代表恒定 TRUE */
				return NULL;
			}
		}
		/* 否则保持不变返回它 */
		return fc_node;
	}
	if (is_notclause(fc_node))
	{
		/* 如果 NOT 的直接参数是 EXISTS，尝试进行转换 */
		SubLink    *fc_sublink = (SubLink *) get_notclausearg((Expr *) fc_node);
		JoinExpr   *fc_j;
		Relids		fc_child_rels;

		if (fc_sublink && IsA(fc_sublink, SubLink))
		{
			if (fc_sublink->subLinkType == EXISTS_SUBLINK)
			{
				if ((fc_j = convert_EXISTS_sublink_to_join(fc_root, fc_sublink, true,
														fc_available_rels1)) != NULL)
				{
					/* 是的；将新连接节点插入连接树 */
					fc_j->larg = *fc_jtlink1;
					*fc_jtlink1 = (Node *) fc_j;
					/* 递归处理提升的连接树节点 */
					fc_j->rarg = fc_pull_up_sublinks_jointree_recurse(fc_root,
																fc_j->rarg,
																&fc_child_rels);

					/*
					 * 现在递归处理提拉的条件。因为
					 * 我们在 NOT 之下，我们不能提拉引用左侧内容的子链接，但仍然可以
					 * 提拉引用 j->rarg 的子链接。
					 */
					fc_j->quals = fc_pull_up_sublinks_qual_recurse(fc_root,
															 fc_j->quals,
															 &fc_j->rarg,
															 fc_child_rels,
															 NULL, NULL);
					/* 返回 NULL 代表恒定 TRUE */
					return NULL;
				}
				if (fc_available_rels2 != NULL &&
					(fc_j = convert_EXISTS_sublink_to_join(fc_root, fc_sublink, true,
														fc_available_rels2)) != NULL)
				{
					/* 是的；将新连接节点插入连接树 */
					fc_j->larg = *fc_jtlink2;
					*fc_jtlink2 = (Node *) fc_j;
					/* 递归处理提升的连接树节点 */
					fc_j->rarg = fc_pull_up_sublinks_jointree_recurse(fc_root,
																fc_j->rarg,
																&fc_child_rels);

					/*
					 * 现在递归处理提拉的条件。因为
					 * 我们在 NOT 之下，我们不能提拉引用左侧内容的子链接，但仍然可以
					 * 提拉引用 j->rarg 的子链接。
					 */
					fc_j->quals = fc_pull_up_sublinks_qual_recurse(fc_root,
															 fc_j->quals,
															 &fc_j->rarg,
															 fc_child_rels,
															 NULL, NULL);
					/* 返回 NULL 代表恒定 TRUE */
					return NULL;
				}
			}
		}
		/* 否则保持不变返回它 */
		return fc_node;
	}
	if (is_andclause(fc_node))
	{
		/* 递归进入 AND 子句 */
		List	   *fc_newclauses = NIL;
		ListCell   *fc_l;

		foreach(fc_l, ((BoolExpr *) fc_node)->args)
		{
			Node	   *fc_oldclause = (Node *) lfirst(fc_l);
			Node	   *fc_newclause;

			fc_newclause = fc_pull_up_sublinks_qual_recurse(fc_root,
													  fc_oldclause,
													  fc_jtlink1,
													  fc_available_rels1,
													  fc_jtlink2,
													  fc_available_rels2);
			if (fc_newclause)
				fc_newclauses = lappend(fc_newclauses, fc_newclause);
		}
		/* 我们可能得到了比开始时更少的条件 */
		if (fc_newclauses == NIL)
			return NULL;
		else if (list_length(fc_newclauses) == 1)
			return (Node *) linitial(fc_newclauses);
		else
			return (Node *) make_andclause(fc_newclauses);
	}
	/* 如果不是 AND 则停止 */
	return fc_node;
}

/*
 * preprocess_function_rtes
 *		对 FROM 子句中的任何 FUNCTION RTE 进行常量简化，然后
 *		尝试“内联”任何返回集的函数。
 *
 * 如果 RTE_FUNCTION rtable 条目调用了只包含简单 SELECT 的返回集函数，
 * 我们可以将 rtable 条目转换为直接暴露 SELECT 的 RTE_SUBQUERY 条目。
 * 如果子查询可以被“提拉”以进行进一步优化，这尤其有用，
 * 但即使不这样做，我们也会这样做，以减少执行器开销。
 *
 * 这必须在我们开始对
 * 子查询进行任何优化之前完成，否则任何这样的步骤不会应用于通过内联获得的子查询。
 * 然而，我们会在 pull_up_sublinks 之后进行，以便可以内联
 * 用于 SubLink 子选择中的任何函数。
 *
 * 在这个阶段应用常量简化的原因是
 * (a) 无论如何我们都需要这样做才能内联一个 SRF，和 (b) 通过现在进行此操作，
 * 我们可以确保 pull_up_constant_function() 将看到常量
 * 如果有常量可以看到。这个方法还保证
 * 每个 FUNCTION RTE 都已被常量简化，从而允许 planner.c 的
 * preprocess_expression() 跳过再次执行此操作。
 *
 * 和大多数规划器一样，这个也可以随意更改其输入数据结构。
 */
void preprocess_function_rtes(PlannerInfo *fc_root)
{
	ListCell   *fc_rt;

	foreach(fc_rt, fc_root->parse->rtable)
	{
		RangeTblEntry *fc_rte = (RangeTblEntry *) lfirst(fc_rt);

		if (fc_rte->rtekind == RTE_FUNCTION)
		{
			Query	   *fc_funcquery;

			/* 应用常量简化 */
			fc_rte->functions = (List *)
				eval_const_expressions(fc_root, (Node *) fc_rte->functions);

			/* 检查扩展的安全性，并在可能的情况下进行扩展 */
			fc_funcquery = inline_set_returning_function(fc_root, fc_rte);
			if (fc_funcquery)
			{
				/* 成功扩展，将 RTE 转换为子查询 */
				fc_rte->rtekind = RTE_SUBQUERY;
				fc_rte->subquery = fc_funcquery;
				fc_rte->security_barrier = false;
				/* 清除在子查询 RTE 中不应设置的字段 */
				fc_rte->functions = NIL;
				fc_rte->funcordinality = false;
			}
		}
	}
}

/*
 * pull_up_subqueries
 *		查找可以提拉到父查询中的 rangetable 中的子查询。
 *		如果子查询没有像分组/聚合这样的特殊特性，那么我们可以将它合并到
 *		父级的联合树中。此外，简单的 UNION ALL 结构的子查询可以
 *		转换为“追加关系”。
 */
void pull_up_subqueries(PlannerInfo *fc_root)
{
	/* 联合树的顶层必须始终是 FromExpr */
	Assert(IsA(fc_root->parse->jointree, FromExpr));
	/* 递归开始时没有包含的连接或追加关系 */
	fc_root->parse->jointree = (FromExpr *)
		fc_pull_up_subqueries_recurse(fc_root, (Node *) fc_root->parse->jointree,
								   NULL, NULL, NULL);
	/* 我们仍然应该有一个 FromExpr */
	Assert(IsA(fc_root->parse->jointree, FromExpr));
}

/*
 * pull_up_subqueries_recurse
 *		pull_up_subqueries 的递归内部。
 *
 * 这递归处理联合树并返回修改后的联合树。
 *
 * 如果这个联合树节点在外连接的任一侧内，则
 * lowest_outer_join 引用最低的 JoinExpr 节点；否则它为 NULL。
 * 我们用这个来限制 LATERAL 子查询的影响。
 *
 * 如果这个联合树节点在外连接的可为空侧内，则
 * lowest_nulling_outer_join 引用最低的 JoinExpr 节点；
 * 否则它为 NULL。这强制对
 * 非可空目标列表项的引用使用 PlaceHolderVar 机制， 但仅限于在
 * 该连接之上的引用。
 *
 * 如果我们正在查看追加关系的成员子查询，
 * containing_appendrel 描述该关系；否则它为 NULL。
 * 这强制对所有非 Var 目标列表项使用 PlaceHolderVar 机制，
 * 并对可以被提拉的内容施加一些额外的限制。
 *
 * 这段代码的一个棘手方面是，如果我们提拉一个子查询，我们必须
 * 替换在整个父查询中引用子查询输出的 Vars，
 * 包括附加到我们当前处理的联合树节点之上的条件！ 
 * 我们通过谨慎维护联合树结构的有效性来处理这个问题，
 * 具体如下：
 * 每当我们递归时，树中的所有条件表达式必须可以从顶层访问，
 * 以防递归调用需要修改它们。
 *
 * 同时请注意，我们不能对整个
 * 联合树放任 pullup_replace_vars，因为这会返回一份经过修改的树；
 * 我们必须只在条件上调用它。这个行为合理地使得在
 * 指针上传递 lowest_outer_join 和 lowest_nulling_outer_join，而不是用其他更间接的方法
 * 来识别最低的外连接，显得合乎逻辑。同样，我们不替换 append_rel_list 的成员，而仅仅是它们的
 * 子结构，因此使用 containing_appendrel 引用是安全的。
 */
static Node * fc_pull_up_subqueries_recurse(PlannerInfo *fc_root, Node *fc_jtnode,
						   JoinExpr *fc_lowest_outer_join,
						   JoinExpr *fc_lowest_nulling_outer_join,
						   AppendRelInfo *fc_containing_appendrel)
{
	/* 由于此函数会递归，可能会导致堆栈溢出。 */
	check_stack_depth();
	/* 另外，由于这有点昂贵，检查查询取消。 */
	CHECK_FOR_INTERRUPTS();

	Assert(fc_jtnode != NULL);
	if (IsA(fc_jtnode, RangeTblRef))
	{
		int			fc_varno = ((RangeTblRef *) fc_jtnode)->rtindex;
		RangeTblEntry *fc_rte = rt_fetch(fc_varno, fc_root->parse->rtable);

		/*
		 * 这是一个子查询RTE吗，若是，子查询是否简单到可以
		 * 被提取？
		 *
		 * 如果我们正在查看一个附加关系成员，除非is_safe_append_member
		 * 说这样，否则我们不能提取它。
		 */
		if (fc_rte->rtekind == RTE_SUBQUERY &&
			fc_is_simple_subquery(fc_root, fc_rte->subquery, fc_rte, fc_lowest_outer_join) &&
			(fc_containing_appendrel == NULL ||
			 fc_is_safe_append_member(fc_rte->subquery)))
			return fc_pull_up_simple_subquery(fc_root, fc_jtnode, fc_rte,
										   fc_lowest_outer_join,
										   fc_lowest_nulling_outer_join,
										   fc_containing_appendrel);

		/*
		 * 或者，它是一个简单的UNION ALL子查询？ 如果是，将其扁平化
		 * 成一个“附加关系”。
		 *
		 * 不管这个查询本身是否是一个附加关系成员，进行扁平化都是安全的。
		 * （如果你认为我们应该尝试将两个层级的附加关系一起扁平化，
		 * 你是对的；但我们在set_append_rel_pathlist中处理那一部分，而不是在这里。）
		 */
		if (fc_rte->rtekind == RTE_SUBQUERY &&
			fc_is_simple_union_all(fc_rte->subquery))
			return fc_pull_up_simple_union_all(fc_root, fc_jtnode, fc_rte);

		/*
		 * 或者也许是一个简单的VALUES RTE？
		 *
		 * 我们不允许VALUES在外连接下提取，也不允许提取到
		 * 附加关系（此类情况在目前也不可能）。
		 */
		if (fc_rte->rtekind == RTE_VALUES &&
			fc_lowest_outer_join == NULL &&
			fc_containing_appendrel == NULL &&
			fc_is_simple_values(fc_root, fc_rte))
			return fc_pull_up_simple_values(fc_root, fc_jtnode, fc_rte);

		/*
		 * 或者也许是一个可以内联的FUNCTION RTE？
		 */
		if (fc_rte->rtekind == RTE_FUNCTION)
			return fc_pull_up_constant_function(fc_root, fc_jtnode, fc_rte,
											 fc_lowest_nulling_outer_join,
											 fc_containing_appendrel);

		/* 否则，在这个节点上不做任何事情。 */
	}
	else if (IsA(fc_jtnode, FromExpr))
	{
		FromExpr   *fc_f = (FromExpr *) fc_jtnode;
		ListCell   *fc_l;

		Assert(fc_containing_appendrel == NULL);
		/* 递归转换所有子节点 */
		foreach(fc_l, fc_f->fromlist)
		{
			lfirst(fc_l) = fc_pull_up_subqueries_recurse(fc_root, lfirst(fc_l),
												   fc_lowest_outer_join,
												   fc_lowest_nulling_outer_join,
												   NULL);
		}
	}
	else if (IsA(fc_jtnode, JoinExpr))
	{
		JoinExpr   *fc_j = (JoinExpr *) fc_jtnode;

		Assert(fc_containing_appendrel == NULL);
		/* 递归，注意在外连接内部时要提醒自己 */
		switch (fc_j->jointype)
		{
			case JOIN_INNER:
				fc_j->larg = fc_pull_up_subqueries_recurse(fc_root, fc_j->larg,
													 fc_lowest_outer_join,
													 fc_lowest_nulling_outer_join,
													 NULL);
				fc_j->rarg = fc_pull_up_subqueries_recurse(fc_root, fc_j->rarg,
													 fc_lowest_outer_join,
													 fc_lowest_nulling_outer_join,
													 NULL);
				break;
			case JOIN_LEFT:
			case JOIN_SEMI:
			case JOIN_ANTI:
				fc_j->larg = fc_pull_up_subqueries_recurse(fc_root, fc_j->larg,
													 fc_j,
													 fc_lowest_nulling_outer_join,
													 NULL);
				fc_j->rarg = fc_pull_up_subqueries_recurse(fc_root, fc_j->rarg,
													 fc_j,
													 fc_j,
													 NULL);
				break;
			case JOIN_FULL:
				fc_j->larg = fc_pull_up_subqueries_recurse(fc_root, fc_j->larg,
													 fc_j,
													 fc_j,
													 NULL);
				fc_j->rarg = fc_pull_up_subqueries_recurse(fc_root, fc_j->rarg,
													 fc_j,
													 fc_j,
													 NULL);
				break;
			case JOIN_RIGHT:
				fc_j->larg = fc_pull_up_subqueries_recurse(fc_root, fc_j->larg,
													 fc_j,
													 fc_j,
													 NULL);
				fc_j->rarg = fc_pull_up_subqueries_recurse(fc_root, fc_j->rarg,
													 fc_j,
													 fc_lowest_nulling_outer_join,
													 NULL);
				break;
			default:
				elog(ERROR, "unrecognized join type: %d",
					 (int) fc_j->jointype);
				break;
		}
	}
	else
		elog(ERROR, "unrecognized node type: %d",
			 (int) nodeTag(fc_jtnode));
	return fc_jtnode;
}

/*
 * pull_up_simple_subquery
 *		尝试提取一个简单的单个子查询。
 *
 * jtnode是一个RangeTblRef，已被pull_up_subqueries
 * 暂定识别为一个简单子查询。我们返回替换的jointree节点，
 * 或者如果我们确定子查询根本不能被提取，则返回jtnode本身。
 *
 * rte是jtnode引用的RangeTblEntry。其余参数与
 * pull_up_subqueries_recurse相同。
 */
static Node * fc_pull_up_simple_subquery(PlannerInfo *fc_root, Node *fc_jtnode, RangeTblEntry *fc_rte,
						JoinExpr *fc_lowest_outer_join,
						JoinExpr *fc_lowest_nulling_outer_join,
						AppendRelInfo *fc_containing_appendrel)
{
	Query	   *fc_parse = fc_root->parse;
	int			fc_varno = ((RangeTblRef *) fc_jtnode)->rtindex;
	Query	   *fc_subquery;
	PlannerInfo *fc_subroot;
	int			fc_rtoffset;
	pullup_replace_vars_context fc_rvcontext;
	ListCell   *fc_lc;

	/*
	 * 创建一个可修改的子查询的副本进行处理，以便在下面决定
	 * 我们最终不能提取它时，RTE将保持不变。
	 */
	fc_subquery = copyObject(fc_rte->subquery);

	/*
	 * 为此子查询创建一个PlannerInfo数据结构。
	 *
	 * 注意：接下来的几个步骤应该与
	 * subquery_planner()中的第一次处理匹配。我们可以重构以避免代码重复吗，
	 * 还是这只会让事情变得更丑？
	 */
	fc_subroot = makeNode(PlannerInfo);
	fc_subroot->parse = fc_subquery;
	fc_subroot->glob = fc_root->glob;
	fc_subroot->query_level = fc_root->query_level;
	fc_subroot->parent_root = fc_root->parent_root;
	fc_subroot->plan_params = NIL;
	fc_subroot->outer_params = NULL;
	fc_subroot->planner_cxt = CurrentMemoryContext;
	fc_subroot->init_plans = NIL;
	fc_subroot->cte_plan_ids = NIL;
	fc_subroot->multiexpr_params = NIL;
	fc_subroot->eq_classes = NIL;
	fc_subroot->ec_merging_done = false;
	fc_subroot->all_result_relids = NULL;
	fc_subroot->leaf_result_relids = NULL;
	fc_subroot->append_rel_list = NIL;
	fc_subroot->row_identity_vars = NIL;
	fc_subroot->rowMarks = NIL;
	memset(fc_subroot->upper_rels, 0, sizeof(fc_subroot->upper_rels));
	memset(fc_subroot->upper_targets, 0, sizeof(fc_subroot->upper_targets));
	fc_subroot->processed_tlist = NIL;
	fc_subroot->update_colnos = NIL;
	fc_subroot->grouping_map = NULL;
	fc_subroot->minmax_aggs = NIL;
	fc_subroot->qual_security_level = 0;
	fc_subroot->hasRecursion = false;
	fc_subroot->wt_param_id = -1;
	fc_subroot->non_recursive_path = NULL;

	/* 没有CTE需要担心 */
	Assert(fc_subquery->cteList == NIL);

	/*
	 * 如果 FROM 子句为空，用一个虚拟的 RTE_RESULT RTE 替换它，
	 * 以便我们不需要处理那么多特殊情况。
	 */
	replace_empty_jointree(fc_subquery);

	/*
	 * 提取子查询中的任何子链接，以便我们不留下未优化的子链接。
	 */
	if (fc_subquery->hasSubLinks)
		pull_up_sublinks(fc_subroot);

	/*
	 * 同样，预处理其函数RTE以内联其范围表中的任何返回集合的函数。
	 */
	preprocess_function_rtes(fc_subroot);

	/*
	 * 递归提取子查询的子查询，以便
	 * pull_up_subqueries的处理在其jointree和
	 * rangetable中完成。
	 *
	 * 注意：子查询的递归开始时包含连接信息为NULL是没问题的，
	 * 即使我们在上层查询中处于外连接中；下层查询以干净的状态开始
	 * 外连接语义。同样，我们不需要传递附加关系状态。
	 */
	pull_up_subqueries(fc_subroot);

	/*
	 * 现在我们必须重新检查子查询是否仍然简单到足够可以拉起。
	 * 如果不行，则放弃处理。
	 *
	 * 我们其实不需要重新检查所有相关条件，但实际上保持这个“if”看起来和
	 * pull_up_subqueries_recurse 中的那个一样更容易。
	 */
	if (fc_is_simple_subquery(fc_root, fc_subquery, fc_rte, fc_lowest_outer_join) &&
		(fc_containing_appendrel == NULL || fc_is_safe_append_member(fc_subquery)))
	{
		/* 可以继续 */
	}
	else
	{
		/*
		 * 放弃，返回未修改的 RangeTblRef。
		 *
		 * 注意：我们刚刚做的工作将在子查询被单独计划时重做。
		 * 或许我们可以通过将修改后的子查询存储回范围表来避免这种情况，
		 * 但我现在不想冒这个风险。
		 */
		return fc_jtnode;
	}

	/*
	 * 我们必须平展子查询目标列表中的任何连接别名变量，
	 * 因为拉起子查询的子查询可能已将它们的扩展更改为任意表达式，
	 * 这可能会影响 pullup_replace_vars 对于是否需要 
	 * PlaceHolderVar 包装器的 tlist 条目的决定。
	 * （可能最好在某个早期阶段对整个查询树执行 flatten_join_alias_vars，
	 * 也许甚至在重写器中；但现在我们就先在这里修复这个案例。）
	 */
	fc_subquery->targetList = (List *)
		flatten_join_alias_vars(fc_subroot->parse, (Node *) fc_subquery->targetList);

	/*
	 * 调整子查询中的级别0 varnos，以便我们可以将其范围表
	 * 附加到上层查询中。我们还必须修复子查询的 append_rel_list。
	 */
	fc_rtoffset = list_length(fc_parse->rtable);
	OffsetVarNodes((Node *) fc_subquery, fc_rtoffset, 0);
	OffsetVarNodes((Node *) fc_subroot->append_rel_list, fc_rtoffset, 0);

	/*
	 * 子查询中的上层变量现在比之前更靠近其父级一层。
	 */
	IncrementVarSublevelsUp((Node *) fc_subquery, -1, 1);
	IncrementVarSublevelsUp((Node *) fc_subroot->append_rel_list, -1, 1);

	/*
	 * 子查询的目标列表项现在处于适当的形式，可以插入到顶层查询中，
	 * 除非我们可能需要将它们包装在 PlaceHolderVars 中。
	 * 设置用于 pullup_replace_vars 的所需上下文数据。
	 */
	fc_rvcontext.root = fc_root;
	fc_rvcontext.targetlist = fc_subquery->targetList;
	fc_rvcontext.target_rte = fc_rte;
	if (fc_rte->lateral)
		fc_rvcontext.relids = get_relids_in_jointree((Node *) fc_subquery->jointree,
												  true);
	else						/* 不需要 relids */
		fc_rvcontext.relids = NULL;
	fc_rvcontext.outer_hasSubLinks = &fc_parse->hasSubLinks;
	fc_rvcontext.varno = fc_varno;
	/* 如果需要，这些标志将在下面设置 */
	fc_rvcontext.need_phvs = false;
	fc_rvcontext.wrap_non_vars = false;
	/* 使用索引 0 .. length(tlist) 初始化缓存数组 */
	fc_rvcontext.rv_cache = palloc0((list_length(fc_subquery->targetList) + 1) *
								 sizeof(Node *));

	/*
	 * 如果我们处于外连接下，则非可空项和横向引用可能需要转换为 PlaceHolderVars。
	 */
	if (fc_lowest_nulling_outer_join != NULL)
		fc_rvcontext.need_phvs = true;

	/*
	 * 如果我们处理的是一个 appendrel 成员，那么任何不是简单 Var 的东西都必须转换为 PlaceHolderVar。
	 * 我们强制这样做，以确保我们拉起的内容不会在后续处理期间合并到周围的表达式中，
	 * 然后无法匹配实际可从 appendrel 中提供的表达式。
	 */
	if (fc_containing_appendrel != NULL)
	{
		fc_rvcontext.need_phvs = true;
		fc_rvcontext.wrap_non_vars = true;
	}

	/*
	 * 如果父查询使用分组集，我们需要为任何不是简单 Var 的东西提供一个 PlaceHolderVar。
	 * 再次，这确保表达式保持其独立性，以便在适当时可以与分组集列匹配。
	 * （仅对分组集列中使用的值进行包装，并且仅在 tlist 和 havingQual 的非聚合部分中进行包装，
	 * 但这将需要大量 pullup_replace_vars 当前未具备的基础设施。）
	 */
	if (fc_parse->groupingSets)
	{
		fc_rvcontext.need_phvs = true;
		fc_rvcontext.wrap_non_vars = true;
	}

	/*
	 * 用调整后的 subtlist 项的副本替换顶层查询对子查询输出的所有引用，
	 * 注意不要替换任何连接树结构。
	 */
	fc_perform_pullup_replace_vars(fc_root, &fc_rvcontext,
								fc_lowest_nulling_outer_join,
								fc_containing_appendrel);

	/*
	 * 如果子查询有一个 LATERAL 标记，则将其传播到可能现在包含横向交叉引用的任何子 RTE。
	 * 这些子项可能包含或不包含任何实际的横向交叉引用，
	 * 但我们必须标记被拉起的子 RTE，以便以后规划器阶段会检查这些。
	 */
	if (fc_rte->lateral)
	{
		foreach(fc_lc, fc_subquery->rtable)
		{
			RangeTblEntry *fc_child_rte = (RangeTblEntry *) lfirst(fc_lc);

			switch (fc_child_rte->rtekind)
			{
				case RTE_RELATION:
					if (fc_child_rte->tablesample)
						fc_child_rte->lateral = true;
					break;
				case RTE_SUBQUERY:
				case RTE_FUNCTION:
				case RTE_VALUES:
				case RTE_TABLEFUNC:
					fc_child_rte->lateral = true;
					break;
				case RTE_JOIN:
				case RTE_CTE:
				case RTE_NAMEDTUPLESTORE:
				case RTE_RESULT:
					/* 这些不能包含任何横向引用 */
					break;
			}
		}
	}

	/*
	 * 现在将调整后的 rtable 条目附加到上层查询。
	 * （我们在修复上层 rtable 条目后再执行此操作；
	 * 对 subquery 条目运行该代码没有意义。）
	 */
	fc_parse->rtable = list_concat(fc_parse->rtable, fc_subquery->rtable);

	/*
	 * 也要拉起任何 FOR UPDATE/SHARE 标记。
	 * （OffsetVarNodes 已经调整了标记 rtindexes，
	 * 所以只需连接列表。）
	 */
	fc_parse->rowMarks = list_concat(fc_parse->rowMarks, fc_subquery->rowMarks);

	/*
	 * 我们还必须修复父查询中任何 PlaceHolderVar 节点的 relid 集。
	 * （这或许可以通过 pullup_replace_vars() 来完成，
	 * 但使用两个遍历似乎更清晰。）
	 * 特别要注意，任何刚刚由 pullup_replace_vars() 创建的 PlaceHolderVar 节点都将被调整，
	 * 因此使用子查询的 varno 创建它们是正确的。
	 *
	 * 同样，出现在 AppendRelInfo 节点中的 relids 也必须被修复。
	 * 我们已经检查过这不会需要将多个 subrelids 引入到单槽 AppendRelInfo 结构中。
	 */
	if (fc_parse->hasSubLinks || fc_root->glob->lastPHId != 0 ||
		fc_root->append_rel_list)
	{
		Relids		fc_subrelids;

		fc_subrelids = get_relids_in_jointree((Node *) fc_subquery->jointree, false);
		fc_substitute_phv_relids((Node *) fc_parse, fc_varno, fc_subrelids);
		fc_fix_append_rel_relids(fc_root->append_rel_list, fc_varno, fc_subrelids);
	}

	/*
	 * 现在将子查询的 AppendRelInfos 添加到我们的列表中。
	 */
	fc_root->append_rel_list = list_concat(fc_root->append_rel_list,
										fc_subroot->append_rel_list);

	/*
	 * 我们不必为外连接信息做等效的簿记，
	 * 因为这尚未设置。placeholder_list 也是如此。
	 */
	Assert(fc_root->join_info_list == NIL);
	Assert(fc_subroot->join_info_list == NIL);
	Assert(fc_root->placeholder_list == NIL);
	Assert(fc_subroot->placeholder_list == NIL);

	/*
	 * 我们不再需要 RTE 对子查询查询树的副本。
	 * 摆脱它在这一查询级别上并没有特别节省；
	 * 但如果这个查询级别又被拉起到父级，
	 * 我们会浪费周期来复制现在未使用的查询树。
	 */
	fc_rte->subquery = NULL;

	
/*
	 * 杂项管理。
	 *
	 * 尽管 replace_rte_variables() 误差地更新了 parse->hasSubLinks
	 * 如果它复制了任何来自子查询目标列表的 SubLinks，我们仍然
	 * 可能在 FUNCTION 和 VALUES RTE 的表达式中添加 SubLinks 到查询中
	 * 从子查询复制上来。因此，还是有必要复制
	 * subquery->hasSubLinks。也许这将来可以改进。
	 */
	fc_parse->hasSubLinks |= fc_subquery->hasSubLinks;

	/* 如果子查询有任何 RLS 条件，现在主查询也有了 */
	fc_parse->hasRowSecurity |= fc_subquery->hasRowSecurity;

	/*
	 * 如果子查询有 hasAggs、hasWindowFuncs 或
	 * hasTargetSRFs，它将不会被提取，因此这些标志不需要处理
	 */

	/*
	 * 返回调整后的子查询连接树以替换父连接树中的 RangeTblRef 项；
	 * 或者，如果 FromExpr 是退化的，直接返回
	 * 它的单个成员。
	 */
	Assert(IsA(fc_subquery->jointree, FromExpr));
	Assert(fc_subquery->jointree->fromlist != NIL);
	if (fc_subquery->jointree->quals == NULL &&
		list_length(fc_subquery->jointree->fromlist) == 1)
		return (Node *) linitial(fc_subquery->jointree->fromlist);

	return (Node *) fc_subquery->jointree;
}

/*
 * pull_up_simple_union_all
 *		提取一个简单的 UNION ALL 子查询。
 *
 * jtnode 是一个被 pull_up_subqueries 确定为简单 UNION ALL
 * 子查询的 RangeTblRef。我们提取叶子子查询并
 * 为联合集构建一个“附加关系”。结果值就是 
 * jtnode，因为我们实际上不需要改变查询的连接树。
 */
static Node * fc_pull_up_simple_union_all(PlannerInfo *fc_root, Node *fc_jtnode, RangeTblEntry *fc_rte)
{
	int			fc_varno = ((RangeTblRef *) fc_jtnode)->rtindex;
	Query	   *fc_subquery = fc_rte->subquery;
	int			fc_rtoffset = list_length(fc_root->parse->rtable);
	List	   *fc_rtable;

	/*
	 * 制作子查询的 rtable 的可修改副本，以便可以调整
	 * 上层 Vars。setOperations 树中没有这样的 Vars，
	 * 因此修复 rtable 应该就足够了。
	 */
	fc_rtable = copyObject(fc_subquery->rtable);

	/*
	 * 子查询中的上层变量现在比之前更接近它们的父级。
	 * 不过我们不必担心偏移 varnos，因为 UNION 叶子查询无法相互交叉引用。
	 */
	IncrementVarSublevelsUp_rtable(fc_rtable, -1, 1);

	/*
	 * 如果 UNION ALL 子查询有一个 LATERAL 标记，则将其传播到所有
	 * 子查询。各个子查询可能有也可能没有任何实际的横向交叉引用，
	 * 但我们必须标记提取上来的子 RTE，以便后续规划阶段会检查此类引用。
	 */
	if (fc_rte->lateral)
	{
		ListCell   *fc_rt;

		foreach(fc_rt, fc_rtable)
		{
			RangeTblEntry *fc_child_rte = (RangeTblEntry *) lfirst(fc_rt);

			Assert(fc_child_rte->rtekind == RTE_SUBQUERY);
			fc_child_rte->lateral = true;
		}
	}

	/*
	 * 将子查询的 RTE 附加到父 rtable。
	 */
	fc_root->parse->rtable = list_concat(fc_root->parse->rtable, fc_rtable);

	/*
	 * 递归扫描子查询的 setOperations 树，并为叶子子查询添加
	 * AppendRelInfo 节点到父级的 append_rel_list 中。还对叶子子查询应用 pull_up_subqueries。
	 */
	Assert(fc_subquery->setOperations);
	fc_pull_up_union_leaf_queries(fc_subquery->setOperations, fc_root, fc_varno, fc_subquery,
							   fc_rtoffset);

	/*
	 * 将父级标记为附加关系。
	 */
	fc_rte->inh = true;

	return fc_jtnode;
}

/*
 * pull_up_union_leaf_queries -- pull_up_simple_union_all 的递归核心
 *
 * 为 setop 树中的每个叶子查询构建一个 AppendRelInfo，然后
 * 对叶子查询应用 pull_up_subqueries。
 *
 * 请注意，setOpQuery 是包含 setOp 节点的 Query，其 tlist
 * 包含对所有 setop 输出列的引用。当从
 * pull_up_simple_union_all 调用时，这 *不是* root->parse，这是
 * 我们要提升到的父查询。
 *
 * parentRTindex 是附加关系父级在 root->parse->rtable 中的索引。
 *
 * 子 RTE 已经被复制到父级。childRToffset
 * 告诉我们它们在父级范围表中的复制位置。当从 flatten_simple_union_all 调用时，childRToffset 为 0，因为子 RTE
 * 已经在 root->parse->rtable 中，不需要任何 RT 索引调整。
 */
static void fc_pull_up_union_leaf_queries(Node *fc_setOp, PlannerInfo *fc_root, int fc_parentRTindex,
						   Query *fc_setOpQuery, int fc_childRToffset)
{
	if (IsA(fc_setOp, RangeTblRef))
	{
		RangeTblRef *fc_rtr = (RangeTblRef *) fc_setOp;
		int			fc_childRTindex;
		AppendRelInfo *fc_appinfo;

		/*
		 * 计算父级范围表中的索引
		 */
		fc_childRTindex = fc_childRToffset + fc_rtr->rtindex;

		/*
		 * 构建合适的 AppendRelInfo，并附加到父级列表中。
		 */
		fc_appinfo = makeNode(AppendRelInfo);
		fc_appinfo->parent_relid = fc_parentRTindex;
		fc_appinfo->child_relid = fc_childRTindex;
		fc_appinfo->parent_reltype = InvalidOid;
		fc_appinfo->child_reltype = InvalidOid;
		fc_make_setop_translation_list(fc_setOpQuery, fc_childRTindex, fc_appinfo);
		fc_appinfo->parent_reloid = InvalidOid;
		fc_root->append_rel_list = lappend(fc_root->append_rel_list, fc_appinfo);

		/*
		 * 递归地对新的子 RTE 应用 pull_up_subqueries。（我们
		 * 必须首先构建 AppendRelInfo，因为这会修改它。）
		 * 请注意，即使我们实际上在外连接下，我们也可以为包含连接的信息传递 NULL，
		 * 因为子表达式不会向上传播到连接。此外，我们忽略了
		 * pull_up_subqueries_recurse() 返回与我们传递的不同的
		 * jointree 节点的可能性；如果确实如此，重要的是
		 * 它在 AppendRelInfo 节点中替换了子 relid。
		 */
		fc_rtr = makeNode(RangeTblRef);
		fc_rtr->rtindex = fc_childRTindex;
		(void) fc_pull_up_subqueries_recurse(fc_root, (Node *) fc_rtr,
										  NULL, NULL, fc_appinfo);
	}
	else if (IsA(fc_setOp, SetOperationStmt))
	{
		SetOperationStmt *fc_op = (SetOperationStmt *) fc_setOp;

		/* 递归以到达叶子查询 */
		fc_pull_up_union_leaf_queries(fc_op->larg, fc_root, fc_parentRTindex, fc_setOpQuery,
								   fc_childRToffset);
		fc_pull_up_union_leaf_queries(fc_op->rarg, fc_root, fc_parentRTindex, fc_setOpQuery,
								   fc_childRToffset);
	}
	else
	{
		elog(ERROR, "unrecognized node type: %d",
			 (int) nodeTag(fc_setOp));
	}
}

/*
 * make_setop_translation_list
 *	  为 UNION ALL 成员建立从父变量到子变量的翻译列表。 （此时这只是一个简单的引用变量列表，
 *	  但如果我们成功提升了成员子查询，变量将被替换为提升的表达式。）
 *	  还创建相当简单的反向翻译数组。
 */
static void fc_make_setop_translation_list(Query *fc_query, int fc_newvarno,
							AppendRelInfo *fc_appinfo)
{
	List	   *fc_vars = NIL;
	AttrNumber *fc_pcolnos;
	ListCell   *fc_l;

	/* 使用所有条目为零初始化反向翻译数组 */
	/* （resjunk 列的条目将保持不变） */
	fc_appinfo->num_child_cols = list_length(fc_query->targetList);
	fc_appinfo->parent_colnos = fc_pcolnos =
		(AttrNumber *) palloc0(fc_appinfo->num_child_cols * sizeof(AttrNumber));

	foreach(fc_l, fc_query->targetList)
	{
		TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_l);

		if (fc_tle->resjunk)
			continue;

		fc_vars = lappend(fc_vars, makeVarFromTargetEntry(fc_newvarno, fc_tle));
		fc_pcolnos[fc_tle->resno - 1] = fc_tle->resno;
	}

	fc_appinfo->translated_vars = fc_vars;
}

/*
 * is_simple_subquery
 *	  检查范围表中的子查询，看看它是否简单到足以
 *	  提升到父查询中。
 *
 * rte 是包含子查询的 RTE_SUBQUERY RangeTblEntry。
 * （请注意，子查询不一定等于 rte->subquery；它可能是
 * 该子查询的处理副本。）
 * lowest_outer_join 是子查询上方的最低外连接，或 NULL。
 */
static bool fc_is_simple_subquery(PlannerInfo *fc_root, Query *fc_subquery, RangeTblEntry *fc_rte,
				   JoinExpr *fc_lowest_outer_join)
{
	/*
	 * 让我们确保这是一种有效的子选择...
	 */
	if (!IsA(fc_subquery, Query) ||
		fc_subquery->commandType != CMD_SELECT)
		elog(ERROR, "subquery is bogus");

	/*
	 * 当前无法提升涉及 setops 的查询（除非是简单的 UNION
	 * ALL，由不同的代码路径处理）。也许在查询树重新设计之后...
	 */
	if (fc_subquery->setOperations)
		return false;

	/*
	 * 无法提升涉及分组、聚合、SRFs、
	 * 排序、限制或 WITH 的子查询。（XXX 可能在以后允许 WITH）
	 *
	 * 我们也不提升具有显式 FOR UPDATE/SHARE
	 * 子句的子查询，因为提升将使锁定在语义上发生在不应该的层次上。
	 * 隐式的 FOR UPDATE/SHARE 是可以的，因为在这种情况下，锁定最初在上层查询中声明。
	 */
	if (fc_subquery->hasAggs ||
		fc_subquery->hasWindowFuncs ||
		fc_subquery->hasTargetSRFs ||
		fc_subquery->groupClause ||
		fc_subquery->groupingSets ||
		fc_subquery->havingQual ||
		fc_subquery->sortClause ||
		fc_subquery->distinctClause ||
		fc_subquery->limitOffset ||
		fc_subquery->limitCount ||
		fc_subquery->hasForUpdate ||
		fc_subquery->cteList)
		return false;

	/*
	 * 如果 RTE 代表安全屏障视图，则不要提升；一旦 RTE 的变量
	 * 在上层查询中散布开来，我们无法防止信息泄漏。
	 */
	if (fc_rte->security_barrier)
		return false;

	/*
	 * 如果子查询是 LATERAL，请检查来自该子查询的提升限制。
	 */
	if (fc_rte->lateral)
	{
		bool		fc_restricted;
		Relids		fc_safe_upper_varnos;

		/*
		 * 子查询的 WHERE 和 JOIN/ON 条件中不得包含对
		 * 高层外连接之外的关系的任何 lateral 引用（包括
		 * 外连接在子查询内部的情况）。在这种情况下，提升将导致
		 * 我们需要将外连接下方的条件推迟到其上方，这可能完全错误，
		 * 并且无论如何都是一种复杂的情况，似乎目前不值得解决。
		 */
		if (fc_lowest_outer_join != NULL)
		{
			fc_restricted = true;
			fc_safe_upper_varnos = get_relids_in_jointree((Node *) fc_lowest_outer_join,
													   true);
		}
		else
		{
			fc_restricted = false;
			fc_safe_upper_varnos = NULL;	/* 不重要 */
		}

		if (fc_jointree_contains_lateral_outer_refs(fc_root,
												 (Node *) fc_subquery->jointree,
												 fc_restricted, fc_safe_upper_varnos))
			return false;

		
/*
		 * 如果上方有外连接，则也不允许提升
		 * 如果子查询的目标列表中有任何对外连接外的关系的引用，因为这些可能会被拉入子查询上方（但在外连接内或下方）的条件中，然后导致类似于上面检查的情况的条件延迟问题。
		 * （如果外部查询条件中没有出现此类引用，我们就不需要阻止提升，但我们在这里没有足够的信息来检查。
		 * 此外，如果我们强制将此类引用包装在 PlaceHolderVars 中，即使它们在最近的外连接下方，也许可以去掉此限制？但这是非常牵强的用法，因此不清楚是否值得为此烦恼。）
		 */
		if (fc_lowest_outer_join != NULL)
		{
			Relids		fc_lvarnos = pull_varnos_of_level(fc_root,
													   (Node *) fc_subquery->targetList,
													   1);

			if (!bms_is_subset(fc_lvarnos, fc_safe_upper_varnos))
				return false;
		}
	}

	/*
	 * 不要提升任何在其目标列表中有波动函数的子查询。
	 * 否则，如果这些函数被复制到上层查询的多个地方，可能会导致对这些函数的多次评估，从而产生意想不到的结果。
	 * （注意：PlaceHolderVar 机制并不完全保证单次评估；否则我们就可以无论如何提升，并且只是将这些项目包装在 PlaceHolderVars 中……）
	 */
	if (contain_volatile_functions((Node *) fc_subquery->targetList))
		return false;

	return true;
}

/*
 * pull_up_simple_values
 *		提升单个简单 VALUES RTE。
 *
 * jtnode 是一个由 pull_up_subqueries 识别为简单 VALUES RTE 的 RangeTblRef。
 * 我们始终返回一个表示 RESULT RTE 的 RangeTblRef 来替换它（所有失败情况应该已经被 is_simple_values() 检测到）。
 * 实际上，我们返回的就是 jtnode，因为我们用 RESULT RTE 替换了 rangetable 中的 VALUES RTE。
 *
 * rte 是 jtnode 引用的 RangeTblEntry。由于 VALUES RTE 的可能使用有限，
 * 我们不需要 pull_up_subqueries_recurse 的其余参数。
 */
static Node * fc_pull_up_simple_values(PlannerInfo *fc_root, Node *fc_jtnode, RangeTblEntry *fc_rte)
{
	Query	   *fc_parse = fc_root->parse;
	int			fc_varno = ((RangeTblRef *) fc_jtnode)->rtindex;
	List	   *fc_values_list;
	List	   *fc_tlist;
	AttrNumber	fc_attrno;
	pullup_replace_vars_context fc_rvcontext;
	ListCell   *fc_lc;

	Assert(fc_rte->rtekind == RTE_VALUES);
	Assert(list_length(fc_rte->values_lists) == 1);

	/*
	 * 需要一个可修改的 VALUES 列表副本，以便进行修改，以防它被多次引用。
	 */
	fc_values_list = copyObject(linitial(fc_rte->values_lists));

	/*
	 * VALUES RTE 中不能包含任何零级的 Vars，更不用说任何连接别名了，因此不需要展开连接别名 Vars。
	 */
	Assert(!contain_vars_of_level((Node *) fc_values_list, 0));

	/*
	 * 为 pullup_replace_vars 设置所需的上下文数据。特别是，
	 * 我们必须使 VALUES 列表看起来像一个子查询目标列表。
	 */
	fc_tlist = NIL;
	fc_attrno = 1;
	foreach(fc_lc, fc_values_list)
	{
		fc_tlist = lappend(fc_tlist,
						makeTargetEntry((Expr *) lfirst(fc_lc),
										fc_attrno,
										NULL,
										false));
		fc_attrno++;
	}
	fc_rvcontext.root = fc_root;
	fc_rvcontext.targetlist = fc_tlist;
	fc_rvcontext.target_rte = fc_rte;
	fc_rvcontext.relids = NULL;
	fc_rvcontext.outer_hasSubLinks = &fc_parse->hasSubLinks;
	fc_rvcontext.varno = fc_varno;
	fc_rvcontext.need_phvs = false;
	fc_rvcontext.wrap_non_vars = false;
	/* 使用索引 0 .. length(tlist) 初始化缓存数组 */
	fc_rvcontext.rv_cache = palloc0((list_length(fc_tlist) + 1) *
								 sizeof(Node *));

	/*
	 * 用调整后的 VALUES 表达式的副本替换顶级查询对 RTE 输出的所有引用，小心不替换任何联接树结构。
	 * 我们可以假设在包围 VALUES RTE 的虚拟查询中没有外连接或 appendrels。
	 */
	fc_perform_pullup_replace_vars(fc_root, &fc_rvcontext, NULL, NULL);

	/*
	 * 应该没有需要修复的 appendrels，也没有外连接，因此没有 PlaceHolderVars。
	 */
	Assert(fc_root->append_rel_list == NIL);
	Assert(fc_root->join_info_list == NIL);
	Assert(fc_root->placeholder_list == NIL);

	/*
	 * 用 RESULT RTE 替换 VALUES RTE。VALUES RTE 是当前查询级别中的唯一 rtable 条目，因此这很简单。
	 */
	Assert(list_length(fc_parse->rtable) == 1);

	/* 创建适当的 RTE */
	fc_rte = makeNode(RangeTblEntry);
	fc_rte->rtekind = RTE_RESULT;
	fc_rte->eref = makeAlias("*RESULT*", NIL);

	/* 替换 rangetable */
	fc_parse->rtable = list_make1(fc_rte);

	/*
我们可以制造一个新的 RangeTblRef，但我们现在拥有的就足够了 */
	Assert(fc_varno == 1);

	return fc_jtnode;
}

/*
 * is_simple_values
 *	  检查范围表中的 VALUES RTE，看它是否足够简单
 *	  以便提取到父查询中。
 *
 * rte 是要检查的 RTE_VALUES RangeTblEntry。
 */
static bool fc_is_simple_values(PlannerInfo *fc_root, RangeTblEntry *fc_rte)
{
	Assert(fc_rte->rtekind == RTE_VALUES);

	/*
	 * 必须恰好有一个 VALUES 列表，否则用 RESULT RTE 替换 VALUES RTE 在语义上是不正确的，
	 * 也不会有唯一的表达式集合可以替换到父查询中。
	 */
	if (list_length(fc_rte->values_lists) != 1)
		return false;

	/*
	 * 因为 VALUES 不能出现在外部连接下（或者至少，如果出现，我们不会尝试提取它），
	 * 我们不需要担心 LATERAL，也不需要担心 PHVs 对 VALUES 输出的有效性。
	 */

	/*
	 * 不要提取包含任何集合返回或易变函数的 VALUES。
	 * 这里的考虑实际上与可提取子查询的目标列表的限制基本相同。
	 */
	if (expression_returns_set((Node *) fc_rte->values_lists) ||
		contain_volatile_functions((Node *) fc_rte->values_lists))
		return false;

	/*
	 * 不要提取该父查询中不是唯一 RTE 的 VALUES。
	 * 这实际上是解析器当前将生成的唯一情况，
	 * 假设这一点为真可以大大简化
	 * pull_up_simple_values()。
	 */
	if (list_length(fc_root->parse->rtable) != 1 ||
		fc_rte != (RangeTblEntry *) linitial(fc_root->parse->rtable))
		return false;

	return true;
}

/*
 * pull_up_constant_function
 *	提取一个已经简化为常量的 RTE_FUNCTION 表达式。
 *
 * jtnode 是一个已被 pull_up_subqueries 识别为 FUNCTION RTE 的 RangeTblRef。
 * 如果它的表达式只是一个 Const，则将该值提升到父查询中，
 * 并用 RTE_RESULT 替换 RTE_FUNCTION。
 *
 * 从原则上讲，我们可以提取任何不可变表达式，但我们不会。
 * 那可能会导致对表达式的多次求值，如果它不仅仅是一个 Const，可能会很昂贵。
 * 此外，这种做法的主要价值在于
 * 让常量参与进一步的常量折叠，当然对于非 Const 的情况是不会发生的。
 *
 * 提取的值可能需要包裹在 PlaceHolderVar 中，
 * 如果 RTE 在外部连接之下或是 appendrel 的一部分；额外的参数显示是否需要这样。
 */
static Node * fc_pull_up_constant_function(PlannerInfo *fc_root, Node *fc_jtnode,
						  RangeTblEntry *fc_rte,
						  JoinExpr *fc_lowest_nulling_outer_join,
						  AppendRelInfo *fc_containing_appendrel)
{
	Query	   *fc_parse = fc_root->parse;
	RangeTblFunction *fc_rtf;
	TypeFuncClass fc_functypclass;
	Oid			fc_funcrettype;
	TupleDesc	fc_tupdesc;
	pullup_replace_vars_context fc_rvcontext;

	/* 如果 RTE 有 ORDINALITY 则失败 - 我们在这里不实现。 */
	if (fc_rte->funcordinality)
		return fc_jtnode;

	/* 如果 RTE 不是单个，简单的 Const 表达式则失败 */
	if (list_length(fc_rte->functions) != 1)
		return fc_jtnode;
	fc_rtf = linitial_node(RangeTblFunction, fc_rte->functions);
	if (!IsA(fc_rtf->funcexpr, Const))
		return fc_jtnode;

	/*
	 * 如果函数的结果不是标量，我们就放弃。
	 * 从原则上讲，我们可以将复合常量值拆分为每列常量，
	 * 但现在看来不值得做这项工作。
	 */
	if (fc_rtf->funccolcount != 1)
		return fc_jtnode;			/* 绝对是复合类型 */

	/* 如果它有coldeflist，它肯定返回RECORD */
	if (fc_rtf->funccolnames != NIL)
		return fc_jtnode;			/* 必须是单列 RECORD 类型 */

	fc_functypclass = get_expr_result_type(fc_rtf->funcexpr,
										&fc_funcrettype,
										&fc_tupdesc);
	if (fc_functypclass != TYPEFUNC_SCALAR)
		return fc_jtnode;			/* 必须是单列复合类型 */

	/* 为应用 pullup_replace_vars 创建上下文 */
	fc_rvcontext.root = fc_root;
	fc_rvcontext.targetlist = list_make1(makeTargetEntry((Expr *) fc_rtf->funcexpr,
													  1,	/* resno */
													  NULL, /* resname */
													  false));	/* resjunk */
	fc_rvcontext.target_rte = fc_rte;

	/*
	 * 由于这个函数被简化为 Const，
	 * 它不包含任何横向引用，即使它被标记为 LATERAL。
	 * 这意味着我们不需要填写 relids。
	 */
	fc_rvcontext.relids = NULL;

	fc_rvcontext.outer_hasSubLinks = &fc_parse->hasSubLinks;
	fc_rvcontext.varno = ((RangeTblRef *) fc_jtnode)->rtindex;
	/* 如果需要，这些标志将在下面设置 */
	fc_rvcontext.need_phvs = false;
	fc_rvcontext.wrap_non_vars = false;
	/* 使用索引 0 .. length(tlist) 初始化缓存数组 */
	fc_rvcontext.rv_cache = palloc0((list_length(fc_rvcontext.targetlist) + 1) *
								 sizeof(Node *));

	/*
	 * 如果我们处于外连接下，则非可空项和横向引用可能需要转换为 PlaceHolderVars。
	 */
	if (fc_lowest_nulling_outer_join != NULL)
		fc_rvcontext.need_phvs = true;

	/*
	 * 如果我们处理的是 appendrel 成员，则任何不是简单 Var 的东西都必须转化为 PlaceHolderVar。
	 * （请参阅 pull_up_simple_subquery() 中的注释。）
	 */
	if (fc_containing_appendrel != NULL)
	{
		fc_rvcontext.need_phvs = true;
		fc_rvcontext.wrap_non_vars = true;
	}

	/*
	 * 如果父查询使用分组集合，
	 * 那么对于任何不是简单 Var 的东西我们都需要一个 PlaceHolderVar。
	 */
	if (fc_parse->groupingSets)
	{
		fc_rvcontext.need_phvs = true;
		fc_rvcontext.wrap_non_vars = true;
	}

	/*
	 * 用 funcexpr 的副本替换顶层查询对 RTE 输出的所有引用，
	 * 注意不要替换任何联接树结构。
	 */
	fc_perform_pullup_replace_vars(fc_root, &fc_rvcontext,
								fc_lowest_nulling_outer_join,
								fc_containing_appendrel);

	/*
	 * 我们不需要费心去更改父查询中的 PlaceHolderVars。
	 * 他们对 RT 索引的引用现在仍然有效，
	 * 如果我们能够删除 RTE_RESULT，将在稍后被移除。
	 */

	/*
	 * 将 RTE 转换为 RTE_RESULT 类型，标志着我们不再需要
	 * 扫描它，清零 RTE_FUNCTION 特定字段。确保
	 * RTE 没有标记为 LATERAL，因为在其他地方我们不希望
	 * RTE_RESULT 是 LATERAL。
	 */
	fc_rte->rtekind = RTE_RESULT;
	fc_rte->functions = NIL;
	fc_rte->lateral = false;

	/*
	 * 我们可以重用 RangeTblRef 节点。
	 */
	return fc_jtnode;
}

/*
 * is_simple_union_all
 *	  检查一个子查询看它是否是一个简单的 UNION ALL。
 *
 * 我们要求所有的集合操作都是 UNION ALL（不混合），
 * 且不能涉及任何数据类型强制转换，即所有的叶查询必须发出
 * 相同的数据类型。
 */
static bool fc_is_simple_union_all(Query *fc_subquery)
{
	SetOperationStmt *fc_topop;

	/* 让我们确保它是一个有效的子选择... */
	if (!IsA(fc_subquery, Query) ||
		fc_subquery->commandType != CMD_SELECT)
		elog(ERROR, "subquery is bogus");

	/* 它到底是一个集合操作查询吗？ */
	fc_topop = castNode(SetOperationStmt, fc_subquery->setOperations);
	if (!fc_topop)
		return false;

	/* 不能处理 ORDER BY、LIMIT/OFFSET、锁定或 WITH */
	if (fc_subquery->sortClause ||
		fc_subquery->limitOffset ||
		fc_subquery->limitCount ||
		fc_subquery->rowMarks ||
		fc_subquery->cteList)
		return false;

	/* 递归检查集合操作的树 */
	return fc_is_simple_union_all_recurse((Node *) fc_topop, fc_subquery,
									   fc_topop->colTypes);
}

static bool fc_is_simple_union_all_recurse(Node *fc_setOp, Query *fc_setOpQuery, List *fc_colTypes)
{
	/* 由于此函数会递归，可能会导致堆栈溢出。 */
	check_stack_depth();

	if (IsA(fc_setOp, RangeTblRef))
	{
		RangeTblRef *fc_rtr = (RangeTblRef *) fc_setOp;
		RangeTblEntry *fc_rte = rt_fetch(fc_rtr->rtindex, fc_setOpQuery->rtable);
		Query	   *fc_subquery = fc_rte->subquery;

		Assert(fc_subquery != NULL);

		/* 叶节点如果与顶层列类型匹配则可以 */
		/* 在这里我们不必比较 typmods 或排序规则 */
		return tlist_same_datatypes(fc_subquery->targetList, fc_colTypes, true);
	}
	else if (IsA(fc_setOp, SetOperationStmt))
	{
		SetOperationStmt *fc_op = (SetOperationStmt *) fc_setOp;

		/* 必须是 UNION ALL */
		if (fc_op->op != SETOP_UNION || !fc_op->all)
			return false;

		/* 递归检查输入 */
		return fc_is_simple_union_all_recurse(fc_op->larg, fc_setOpQuery, fc_colTypes) &&
			fc_is_simple_union_all_recurse(fc_op->rarg, fc_setOpQuery, fc_colTypes);
	}
	else
	{
		elog(ERROR, "unrecognized node type: %d",
			 (int) nodeTag(fc_setOp));
		return false;			/* 保持编译器安静 */
	}
}

/*
 * is_safe_append_member
 *	  检查一个作为 UNION ALL appendrel 叶子的子查询，以确定是否可以安全地提取。
 */
static bool fc_is_safe_append_member(Query *fc_subquery)
{
	FromExpr   *fc_jtnode;

	/*
	 * 只有在其 jointree 精确包含一个 RTE 时，才能安全提取子节点，否则 AppendRelInfo 数据结构会破坏。一个基础 RTE 可能深埋在多个 FromExpr 的层次中。此外，如果子节点的 jointree 完全为空，我们可以提取，因为 pull_up_simple_subquery 会插入一个单一的 RTE_RESULT RTE。
	 *
	 * 此外，子节点不能有任何 WHERE 资格，因为在 appendrel 中没有地方放置它们。（这有点烦人……）如果不需要检查这一点，我们只需测试 get_relids_in_jointree() 是否返回单一集合，以便与 fix_append_rel_relids() 的编码更加一致。
	 */
	fc_jtnode = fc_subquery->jointree;
	Assert(IsA(fc_jtnode, FromExpr));
	/* 检查完全空的情况 */
	if (fc_jtnode->fromlist == NIL && fc_jtnode->quals == NULL)
		return true;
	/* 检查更普遍的情况 */
	while (IsA(fc_jtnode, FromExpr))
	{
		if (fc_jtnode->quals != NULL)
			return false;
		if (list_length(fc_jtnode->fromlist) != 1)
			return false;
		fc_jtnode = linitial(fc_jtnode->fromlist);
	}
	if (!IsA(fc_jtnode, RangeTblRef))
		return false;

	return true;
}

/*
 * jointree_contains_lateral_outer_refs
 *		检查 jointree 的 quals 中是否存在不允许的侧向引用。
 *
 * 如果 restricted 为 false，则允许所有级别-1 的 Vars（但我们仍然必须搜索 jointree，因为它可能包含下面的外部连接，其中将会有限制）。如果 restricted 为 true，当 jointree 中的任何 qualifiers 包含来自 safe_upper_varnos 列表外部的级别-1 Vars 时返回 true。
 */
static bool fc_jointree_contains_lateral_outer_refs(PlannerInfo *fc_root, Node *fc_jtnode,
									 bool fc_restricted,
									 Relids fc_safe_upper_varnos)
{
	if (fc_jtnode == NULL)
		return false;
	if (IsA(fc_jtnode, RangeTblRef))
		return false;
	else if (IsA(fc_jtnode, FromExpr))
	{
		FromExpr   *fc_f = (FromExpr *) fc_jtnode;
		ListCell   *fc_l;

		/* 首先，递归检查子连接 */
		foreach(fc_l, fc_f->fromlist)
		{
			if (fc_jointree_contains_lateral_outer_refs(fc_root,
													 lfirst(fc_l),
													 fc_restricted,
													 fc_safe_upper_varnos))
				return true;
		}

		/* 然后检查顶层的 quals */
		if (fc_restricted &&
			!bms_is_subset(pull_varnos_of_level(fc_root, fc_f->quals, 1),
						   fc_safe_upper_varnos))
			return true;
	}
	else if (IsA(fc_jtnode, JoinExpr))
	{
		JoinExpr   *fc_j = (JoinExpr *) fc_jtnode;

		/*
		 * 如果这是一个外部连接，我们不能允许在其上方或下方有任何上层侧向引用。
		 */
		if (fc_j->jointype != JOIN_INNER)
		{
			fc_restricted = true;
			fc_safe_upper_varnos = NULL;
		}

		/* 检查子连接 */
		if (fc_jointree_contains_lateral_outer_refs(fc_root,
												 fc_j->larg,
												 fc_restricted,
												 fc_safe_upper_varnos))
			return true;
		if (fc_jointree_contains_lateral_outer_refs(fc_root,
												 fc_j->rarg,
												 fc_restricted,
												 fc_safe_upper_varnos))
			return true;

		/* 检查 JOIN 的资格子句 */
		if (fc_restricted &&
			!bms_is_subset(pull_varnos_of_level(fc_root, fc_j->quals, 1),
						   fc_safe_upper_varnos))
			return true;
	}
	else
		elog(ERROR, "unrecognized node type: %d",
			 (int) nodeTag(fc_jtnode));
	return false;
}

/*
 * 在查询树的每个需要的地方执行 pullup_replace_vars。
 *
 * 调用者已经用描述要替代的 Vars 参考目标子查询的数据填充了 *rvcontext。此外，我们需要能够将目标子查询置为 null 的最低外部连接的身份，以及其包含的 appendrel（如果有的话）。
 */
static void fc_perform_pullup_replace_vars(PlannerInfo *fc_root,
							pullup_replace_vars_context *fc_rvcontext,
							JoinExpr *fc_lowest_nulling_outer_join,
							AppendRelInfo *fc_containing_appendrel)
{
	Query	   *fc_parse = fc_root->parse;
	ListCell   *fc_lc;

	/*
	 * 用调整过的 subtlist 项目的副本替换所有顶层查询对子查询输出的引用，注意不要替换任何 jointree 结构。（如果我们可以使用 query_tree_mutator，代码将更整洁。）我们必须在 targetList、returningList 和 havingQual 中使用 PHVs，因为这些肯定位于任何外部连接之上。replace_vars_in_jointree 跟踪其在 jointree 中的位置，并适当地使用 PHVs 或不使用 PHVs。
	 */
	fc_parse->targetList = (List *)
		fc_pullup_replace_vars((Node *) fc_parse->targetList, fc_rvcontext);
	fc_parse->returningList = (List *)
		fc_pullup_replace_vars((Node *) fc_parse->returningList, fc_rvcontext);

	foreach(fc_lc, fc_parse->windowClause)
	{
		WindowClause *fc_wc = lfirst_node(WindowClause, fc_lc);

		if (fc_wc->runCondition != NIL)
			fc_wc->runCondition = (List *)
				fc_pullup_replace_vars((Node *) fc_wc->runCondition, fc_rvcontext);
	}
	if (fc_parse->onConflict)
	{
		fc_parse->onConflict->onConflictSet = (List *)
			fc_pullup_replace_vars((Node *) fc_parse->onConflict->onConflictSet,
								fc_rvcontext);
		fc_parse->onConflict->onConflictWhere =
			fc_pullup_replace_vars(fc_parse->onConflict->onConflictWhere,
								fc_rvcontext);

		/*
		 * 我们假设 ON CONFLICT 的 arbiterElems、arbiterWhere、exclRelTlist 不能包含对子查询的任何引用。
		 */
	}
	if (fc_parse->mergeActionList)
	{
		foreach(fc_lc, fc_parse->mergeActionList)
		{
			MergeAction *fc_action = lfirst(fc_lc);

			fc_action->qual = fc_pullup_replace_vars(fc_action->qual, fc_rvcontext);
			fc_action->targetList = (List *)
				fc_pullup_replace_vars((Node *) fc_action->targetList, fc_rvcontext);
		}
	}
	fc_replace_vars_in_jointree((Node *) fc_parse->jointree, fc_rvcontext,
							 fc_lowest_nulling_outer_join);
	Assert(fc_parse->setOperations == NULL);
	fc_parse->havingQual = fc_pullup_replace_vars(fc_parse->havingQual, fc_rvcontext);

	/*
	 * 替换在 appendrels 的 translated_vars 列表中的引用。当提取一个 appendrel 成员时，我们不需要在父级 appendrel 的列表中使用 PHVs —— 之间没有外部连接。其他地方，出于安全考虑，使用 PHVs。（此分析可以更严格，但似乎不值得过多麻烦。）
	 */
	foreach(fc_lc, fc_root->append_rel_list)
	{
		AppendRelInfo *fc_appinfo = (AppendRelInfo *) lfirst(fc_lc);
		bool		fc_save_need_phvs = fc_rvcontext->need_phvs;

		if (fc_appinfo == fc_containing_appendrel)
			fc_rvcontext->need_phvs = false;
		fc_appinfo->translated_vars = (List *)
			fc_pullup_replace_vars((Node *) fc_appinfo->translated_vars, fc_rvcontext);
		fc_rvcontext->need_phvs = fc_save_need_phvs;
	}

	/*
	 * 替换在连接 RTEs 的 joinaliasvars 列表中的引用。
	 *
	 * 你可能会认为我们可以避免在 lowest_nulling_outer_join 下面的连接的别名 Vars 中使用 PHVs，但这行不通，因为别名 Vars 可能在那个连接之上被引用；我们需要 PHVs 以便在别名 Vars 被扁平化后存在这样的引用。（有可能在未来值得尝试更聪明的做法。）
	 */
	foreach(fc_lc, fc_parse->rtable)
	{
		RangeTblEntry *fc_otherrte = (RangeTblEntry *) lfirst(fc_lc);

		if (fc_otherrte->rtekind == RTE_JOIN)
			fc_otherrte->joinaliasvars = (List *)
				fc_pullup_replace_vars((Node *) fc_otherrte->joinaliasvars,
									fc_rvcontext);
	}
}

/*
 * perform_pullup_replace_vars 的助手例程：对 jointree 中的每个表达式执行 pullup_replace_vars，而不改变 jointree 结构本身。虽然很复杂，但别无他法……
 *
 * 如果我们在或低于最低 nulling outer join，便可以抑制用在替换表达式周围的 PlaceHolderVars。
 */
static void fc_replace_vars_in_jointree(Node *fc_jtnode,
						 pullup_replace_vars_context *fc_context,
						 JoinExpr *fc_lowest_nulling_outer_join)
{
	if (fc_jtnode == NULL)
		return;
	if (IsA(fc_jtnode, RangeTblRef))
	{
		/*
		 * 如果 RangeTblRef 引用一个 LATERAL 子查询（而且不是我们正在提取的同一个子查询），它可能包含对目标子查询的引用，我们必须替换这些引用。我们从 jointree 扫描中驱动这一点，而不是 rtable 扫描，原因有几个：我们可以避免处理不再被引用的 RTE，并且我们可以根据 RTE 是否在可能导致 null 的外部连接之上来使用适当的 need_phvs 设置。
		 */
		int			fc_varno = ((RangeTblRef *) fc_jtnode)->rtindex;

		if (fc_varno != fc_context->varno)	/* 忽略目标子查询本身 */
		{
			RangeTblEntry *fc_rte = rt_fetch(fc_varno, fc_context->root->parse->rtable);

			Assert(fc_rte != fc_context->target_rte);
			if (fc_rte->lateral)
			{
				switch (fc_rte->rtekind)
				{
					case RTE_RELATION:
						/* 除非是 tablesample，否则不应标记为 LATERAL */
						Assert(fc_rte->tablesample);
						fc_rte->tablesample = (TableSampleClause *)
							fc_pullup_replace_vars((Node *) fc_rte->tablesample,
												fc_context);
						break;
					case RTE_SUBQUERY:
						fc_rte->subquery =
							fc_pullup_replace_vars_subquery(fc_rte->subquery,
														 fc_context);
						break;
					case RTE_FUNCTION:
						fc_rte->functions = (List *)
							fc_pullup_replace_vars((Node *) fc_rte->functions,
												fc_context);
						break;
					case RTE_TABLEFUNC:
						fc_rte->tablefunc = (TableFunc *)
							fc_pullup_replace_vars((Node *) fc_rte->tablefunc,
												fc_context);
						break;
					case RTE_VALUES:
						fc_rte->values_lists = (List *)
							fc_pullup_replace_vars((Node *) fc_rte->values_lists,
												fc_context);
						break;
					case RTE_JOIN:
					case RTE_CTE:
					case RTE_NAMEDTUPLESTORE:
					case RTE_RESULT:
						/* 这些不应标记为 LATERAL */
						Assert(false);
						break;
				}
			}
		}
	}
	else if (IsA(fc_jtnode, FromExpr))
	{
		FromExpr   *fc_f = (FromExpr *) fc_jtnode;
		ListCell   *fc_l;

		foreach(fc_l, fc_f->fromlist)
			fc_replace_vars_in_jointree(lfirst(fc_l), fc_context,
									 fc_lowest_nulling_outer_join);
		fc_f->quals = fc_pullup_replace_vars(fc_f->quals, fc_context);
	}
	else if (IsA(fc_jtnode, JoinExpr))
	{
		JoinExpr   *fc_j = (JoinExpr *) fc_jtnode;
		bool		fc_save_need_phvs = fc_context->need_phvs;

		if (fc_j == fc_lowest_nulling_outer_join)
		{
			/* 在此连接内或下方没有更多的 PHVs */
			fc_context->need_phvs = false;
			fc_lowest_nulling_outer_join = NULL;
		}
		fc_replace_vars_in_jointree(fc_j->larg, fc_context, fc_lowest_nulling_outer_join);
		fc_replace_vars_in_jointree(fc_j->rarg, fc_context, fc_lowest_nulling_outer_join);

		/*
		 * 在全连接的 join quals 中使用 PHVs，即使它是最低的 nulling outer join。否则，我们无法识别提取的无变量表达式来自连接的哪一侧，这可能导致根本无法创建计划，因为没有任何的 quals 似乎是可合并或可哈希的条件。为此，我们不关心 wrap_non_vars 的状态，因此保持不变。
		 */
		if (fc_j->jointype == JOIN_FULL)
			fc_context->need_phvs = true;

		fc_j->quals = fc_pullup_replace_vars(fc_j->quals, fc_context);

		
/*
		 * 我们不 bother 更新 colvars 列表，因为它不会再被使用
		 * ...
		 */
		fc_context->need_phvs = fc_save_need_phvs;
	}
	else
		elog(ERROR, "unrecognized node type: %d",
			 (int) nodeTag(fc_jtnode));
}

/*
 * 在整个表达式树中应用 pullup 变量替换
 *
 * 返回树的修改副本，因此不能在需要就地替换的地方使用。
 */
static Node * fc_pullup_replace_vars(Node *fc_expr, pullup_replace_vars_context *fc_context)
{
	return replace_rte_variables(fc_expr,
								 fc_context->varno, 0,
								 fc_pullup_replace_vars_callback,
								 (void *) fc_context,
								 fc_context->outer_hasSubLinks);
}

static Node * fc_pullup_replace_vars_callback(Var *fc_var,
							 replace_rte_variables_context *fc_context)
{
	pullup_replace_vars_context *fc_rcon = (pullup_replace_vars_context *) fc_context->callback_arg;
	int			fc_varattno = fc_var->varattno;
	Node	   *fc_newnode;

	/*
	 * 如果需要 PlaceHolderVars，我们会将修改后的表达式缓存到
	 * rcon->rv_cache[] 中。这并不是期望在此函数内获得任何实质性的速度提升，
	 * 而是为了避免生成具有不同 ID 的相同 PHV。那样会导致运行时重复评估，
	 * 并可能阻止依赖于识别同一子查询输出的不同引用为相等的优化。因此值得
	 * 多花一点额外的精力来避免这种情况。
	 */
	if (fc_rcon->need_phvs &&
		fc_varattno >= InvalidAttrNumber &&
		fc_varattno <= list_length(fc_rcon->targetlist) &&
		fc_rcon->rv_cache[fc_varattno] != NULL)
	{
		/* 只需复制条目并继续调整其 varlevelsup */
		fc_newnode = copyObject(fc_rcon->rv_cache[fc_varattno]);
	}
	else if (fc_varattno == InvalidAttrNumber)
	{
		/* 必须将整个元组引用展开为 RowExpr */
		RowExpr    *fc_rowexpr;
		List	   *fc_colnames;
		List	   *fc_fields;
		bool		fc_save_need_phvs = fc_rcon->need_phvs;
		int			fc_save_sublevelsup = fc_context->sublevels_up;

		/*
		 * 如果为命名行类型的变量生成扩展（即，这是一个普通关系 RTE），
		 * 则必须为被丢弃的列包含虚拟项。如果变量是 RECORD（即，这是一个 JOIN），
		 * 则省略被丢弃的列。在后者的情况下，将列名附加到 RowExpr 以供执行器和 ruleutils.c 使用。
		 *
		 * 为了能够缓存结果，我们始终生成 varlevelsup = 0 的扩展，
		 * 然后根据需要进行调整。
		 */
		expandRTE(fc_rcon->target_rte,
				  fc_var->varno, 0 /* 不是 varlevelsup */ , fc_var->location,
				  (fc_var->vartype != RECORDOID),
				  &fc_colnames, &fc_fields);
		/* 调整生成的每个字段 Vars，但不要插入 PHVs */
		fc_rcon->need_phvs = false;
		fc_context->sublevels_up = 0;	/* 以匹配expandRTE输出 */
		fc_fields = (List *) replace_rte_variables_mutator((Node *) fc_fields,
														fc_context);
		fc_rcon->need_phvs = fc_save_need_phvs;
		fc_context->sublevels_up = fc_save_sublevelsup;

		fc_rowexpr = makeNode(RowExpr);
		fc_rowexpr->args = fc_fields;
		fc_rowexpr->row_typeid = fc_var->vartype;
		fc_rowexpr->row_format = COERCE_IMPLICIT_CAST;
		fc_rowexpr->colnames = (fc_var->vartype == RECORDOID) ? fc_colnames : NIL;
		fc_rowexpr->location = fc_var->location;
		fc_newnode = (Node *) fc_rowexpr;

		/*
		 * 如果需要，插入PlaceHolderVar。请注意，我们是将一个
		 * PlaceHolderVar包裹在整个RowExpr周围，而不是将一个
		 * 包裹在行的每个元素周围。这是因为我们需要这个
		 * 表达式在外部连接强制为NULL时返回NULL，而不是ROW(NULL,NULL,...)
		 */
		if (fc_rcon->need_phvs)
		{
			/* RowExpr当然不是严格的，所以总是需要PHV */
			fc_newnode = (Node *)
				make_placeholder_expr(fc_rcon->root,
									  (Expr *) fc_newnode,
									  bms_make_singleton(fc_rcon->varno));
			/* 用PHV进行缓存，并且varlevelsup仍然为零 */
			fc_rcon->rv_cache[InvalidAttrNumber] = copyObject(fc_newnode);
		}
	}
	else
	{
		/* 正常情况下引用一个目标列表元素 */
		TargetEntry *fc_tle = get_tle_by_resno(fc_rcon->targetlist, fc_varattno);

		if (fc_tle == NULL)		/* 不应该发生 */
			elog(ERROR, "could not find attribute %d in subquery targetlist",
				 fc_varattno);

		/* 复制tlist项目以返回 */
		fc_newnode = (Node *) copyObject(fc_tle->expr);

		/* 如果需要，插入PlaceHolderVar */
		if (fc_rcon->need_phvs)
		{
			bool		fc_wrap;

			if (fc_newnode && IsA(fc_newnode, Var) &&
				((Var *) fc_newnode)->varlevelsup == 0)
			{
				/*
				 * 简单变量总是能够逃避包装，除非它们是
				 * 对外部子查询中某些内容的横向引用。(即使那样，如果
				 * 引用的关系在同一最低外连接下，我们可以省略
				 * PlaceHolderVar，但检查这个似乎没有必要。)
				 */
				if (fc_rcon->target_rte->lateral &&
					!bms_is_member(((Var *) fc_newnode)->varno, fc_rcon->relids))
					fc_wrap = true;
				else
					fc_wrap = false;
			}
			else if (fc_newnode && IsA(fc_newnode, PlaceHolderVar) &&
					 ((PlaceHolderVar *) fc_newnode)->phlevelsup == 0)
			{
				/* 对于PlaceHolderVar适用相同的规则 */
				if (fc_rcon->target_rte->lateral &&
					!bms_is_subset(((PlaceHolderVar *) fc_newnode)->phrels,
								   fc_rcon->relids))
					fc_wrap = true;
				else
					fc_wrap = false;
			}
			else if (fc_rcon->wrap_non_vars)
			{
				/* 将所有非变量包装在PlaceHolderVar中 */
				fc_wrap = true;
			}
			else
			{
				/*
				 * 如果它包含被提取的子查询的变量，并且
				 * 不包含任何非严格构造，那么它肯定是可空的，因此我们不需要插入一个
				 * PlaceHolderVar。
				 *
				 * 这个分析可以更严格：特别是，隐藏在
				 * 更低级的PlaceHolderVar中的非严格构造并不是
				 * 添加另一个PHV的理由。但目前似乎没有
				 * 更精确的代码的价值。
				 *
				 * 注意：将来我们也许应该插入一个
				 * PlaceHolderVar，如果tlist项目的评估成本很高？
				 *
				 * 对于LATERAL子查询，我们必须检查节点的实际变量
				 * 成员资格，但如果它不是横向的，则任何
				 * 级零变量必须属于子查询。
				 */
				if ((fc_rcon->target_rte->lateral ?
					 bms_overlap(pull_varnos(fc_rcon->root, (Node *) fc_newnode),
								 fc_rcon->relids) :
					 contain_vars_of_level((Node *) fc_newnode, 0)) &&
					!contain_nonstrict_functions((Node *) fc_newnode))
				{
					/* 不需要包装 */
					fc_wrap = false;
				}
				else
				{
					/* 否则将其包装在PlaceHolderVar中 */
					fc_wrap = true;
				}
			}

			if (fc_wrap)
				fc_newnode = (Node *)
					make_placeholder_expr(fc_rcon->root,
										  (Expr *) fc_newnode,
										  bms_make_singleton(fc_rcon->varno));

			/*
			 * 如果可能，请缓存它（即，如果attno在范围内，
			 * 这可能应该总是如此）。即使我们决定不需要PHV，
			 * 也可以缓存这个值，因为这个结果将适合
			 * 任何需要需要_phvs的请求。
			 */
			if (fc_varattno > InvalidAttrNumber &&
				fc_varattno <= list_length(fc_rcon->targetlist))
				fc_rcon->rv_cache[fc_varattno] = copyObject(fc_newnode);
		}
	}

	/* 如果tlist项目来自更高的查询，必须调整varlevelsup */
	if (fc_var->varlevelsup > 0)
		IncrementVarSublevelsUp(fc_newnode, fc_var->varlevelsup, 0);

	return fc_newnode;
}

/*
 * 对子查询应用提取变量替换
 *
 * 这需要与pullup_replace_vars()不同，因为
 * replace_rte_variables会认为在进入查询之前不应该递增sublevels_up；
 * 因此，我们需要以sublevels_up == 1调用它。
 */
static Query * fc_pullup_replace_vars_subquery(Query *fc_query,
							 pullup_replace_vars_context *fc_context)
{
	Assert(IsA(fc_query, Query));
	return (Query *) replace_rte_variables((Node *) fc_query,
										   fc_context->varno, 1,
										   fc_pullup_replace_vars_callback,
										   (void *) fc_context,
										   NULL);
}


/*
 * flatten_simple_union_all
 *		尝试将顶层的UNION ALL结构优化为appendrel
 *
 * 如果查询的setOperations树完全由简单的UNION ALL
 * 操作组成，则将其展平为append关系，我们可以
 * 更智能地处理这种情况，而不是通用的setops情况。否则，不做任何事情。
 *
 * 在大多数情况下，这只能针对顶层查询成功，因为对于FROM中的子查询，
 * 父查询的提取子查询的调用已经通过pull_up_simple_union_all展平了UNION。
 * 但这里有几个情况我们可以支持，但在那段代码路径中不行，例如
 * 当子查询还包含ORDER BY时。
 */
void flatten_simple_union_all(PlannerInfo *fc_root)
{
	Query	   *fc_parse = fc_root->parse;
	SetOperationStmt *fc_topop;
	Node	   *fc_leftmostjtnode;
	int			fc_leftmostRTI;
	RangeTblEntry *fc_leftmostRTE;
	int			fc_childRTI;
	RangeTblEntry *fc_childRTE;
	RangeTblRef *fc_rtr;

	/* 不应该调用除非查询有setops */
	fc_topop = castNode(SetOperationStmt, fc_parse->setOperations);
	Assert(fc_topop);

	/* 不能优化掉递归UNION */
	if (fc_root->hasRecursion)
		return;

	/*
	 * 递归检查set operations的树。如果不是所有的UNION ALL
	 * 具有相同的列类型，则放弃。
	 */
	if (!fc_is_simple_union_all_recurse((Node *) fc_topop, fc_parse, fc_topop->colTypes))
		return;

	/*
	 * 在setops树中找到最左边的叶子查询。上层查询的
	 * 变量都引用这个RTE（参见transformSetOperationStmt）。
	 */
	fc_leftmostjtnode = fc_topop->larg;
	while (fc_leftmostjtnode && IsA(fc_leftmostjtnode, SetOperationStmt))
		fc_leftmostjtnode = ((SetOperationStmt *) fc_leftmostjtnode)->larg;
	Assert(fc_leftmostjtnode && IsA(fc_leftmostjtnode, RangeTblRef));
	fc_leftmostRTI = ((RangeTblRef *) fc_leftmostjtnode)->rtindex;
	fc_leftmostRTE = rt_fetch(fc_leftmostRTI, fc_parse->rtable);
	Assert(fc_leftmostRTE->rtekind == RTE_SUBQUERY);

	/*
	 * 复制最左边的RTE并将其添加到rtable中。这个副本
	 * 将代表最左边的叶子查询，作为appendrel的成员。
	 * 原始的将代表整个appendrel。
	 * （我们必须以这种方式做事情，因为上层查询的变量必须被
	 * 视为引用整个appendrel。）
	 */
	fc_childRTE = copyObject(fc_leftmostRTE);
	fc_parse->rtable = lappend(fc_parse->rtable, fc_childRTE);
	fc_childRTI = list_length(fc_parse->rtable);

	/* 修改setops树以引用子副本 */
	((RangeTblRef *) fc_leftmostjtnode)->rtindex = fc_childRTI;

	/* 修改之前的最左RTE以将其标记为appendrel父项 */
	fc_leftmostRTE->inh = true;

	/*
	 * 为appendrel形成RangeTblRef，并将其插入到FROM中。
	 * setops树的顶层查询应该最初具有一个空的FromClause。
	 */
	fc_rtr = makeNode(RangeTblRef);
	fc_rtr->rtindex = fc_leftmostRTI;
	Assert(fc_parse->jointree->fromlist == NIL);
	fc_parse->jointree->fromlist = list_make1(fc_rtr);

	/*
	 * 现在假装查询没有setops。我们必须在尝试
	 * 提取子查询之前这样做，因为pull_up_simple_subquery中的断言。
	 */
	fc_parse->setOperations = NULL;

	/*
	 * 构建AppendRelInfo信息，并对UNION ALL的
	 * 叶子查询应用提取子查询。（我们现在必须这样做
	 * 因为它们之前没有被jointree引用，因此在
	 * pull_up_subqueries的主要调用中被遗漏了。）
	 */
	fc_pull_up_union_leaf_queries((Node *) fc_topop, fc_root, fc_leftmostRTI, fc_parse, 0);
}



/*
 * reduce_outer_joins
 *		尝试将外连接简化为普通的内部连接。
 *
 * 这里的想法是，对于像这样的查询
 *		SELECT ... FROM a LEFT JOIN b ON (...) WHERE b.y = 42;
 * 如果 WHERE 中的 "=" 运算符是严格的，我们可以将 LEFT JOIN 简化为普通的 JOIN。严格运算符总是返回 NULL，导致外部 WHERE 在 LEFT JOIN 为 b 的列填充 NULL 的任何行上失败。因此，连接不需要首先产生包含 NULL 的扩展行 —— 这使其成为普通连接而不是外连接。
 * （这种情况在手动编写的查询中可能不太可能，但在将条件下推到复杂视图时相对可能。）
 *
 * 更一般地，如果在连接树中有一个严格的条件限制了来自连接可空一侧的 Var 为非空，则外连接的强度可以减少。（对于 FULL 连接，这适用于每一侧。）
 *
 * 我们在这里应用的另一个转换是识别像这样的情况
 *		SELECT ... FROM a LEFT JOIN b ON (a.x = b.y) WHERE b.y IS NULL;
 * 如果连接子句对 b.y 是严格的，那么只有扩展为 NULL 的行才能通过上层的 WHERE，我们可以得出查询实际上指定的是一个反半连接。我们将连接类型从 JOIN_LEFT 更改为 JOIN_ANTI。IS NULL 子句因此变得冗余，必须被移除以防止虚假的选择性计算，但我们将其留给 distribute_qual_to_rels 来去掉这样的子句。
 *
 * 此外，我们通过将 JOIN_RIGHT 情况翻转为 JOIN_LEFT 来去掉它们。这在这里和一些后来的规划器例程中节省了一些代码，但这样做的主要原因是为了不需要发明一个 JOIN_REVERSE_ANTI 连接类型。
 *
 * 为了更容易识别严格的条件子句，我们要求在表达式预处理后（即，条件规范化和 JOIN 别名变量扩展）运行这个例程。
 */
void reduce_outer_joins(PlannerInfo *fc_root)
{
	reduce_outer_joins_state *fc_state;

	/*
	 * 为了避免对不必要的更严格性检查，我们希望在当前点以下没有外连接时停止下降连接树。
	 * 这个考虑迫使我们采用两次处理过程。
	 * 第一次处理收集关于哪些基本关系出现在每个连接子句的每一侧下方的信息，以及每个连接子句的每一侧下方是否存在外连接。第二次处理检查条件子句并在向下遍历树时更改连接类型。
	 */
	fc_state = fc_reduce_outer_joins_pass1((Node *) fc_root->parse->jointree);

	/* planner.c 如果没有外连接就不应该调用我 */
	if (fc_state == NULL || !fc_state->contains_outer)
		elog(ERROR, "so where are the outer joins?");

	fc_reduce_outer_joins_pass2((Node *) fc_root->parse->jointree,
							 fc_state, fc_root, NULL, NIL, NIL);
}

/*
 * reduce_outer_joins_pass1 - 第一阶段数据收集
 *
 * 返回一个描述给定连接树节点的状态节点。
 */
static reduce_outer_joins_state *
fc_reduce_outer_joins_pass1(Node *fc_jtnode)
{
	reduce_outer_joins_state *fc_result;

	fc_result = (reduce_outer_joins_state *)
		palloc(sizeof(reduce_outer_joins_state));
	fc_result->relids = NULL;
	fc_result->contains_outer = false;
	fc_result->sub_states = NIL;

	if (fc_jtnode == NULL)
		return fc_result;
	if (IsA(fc_jtnode, RangeTblRef))
	{
		int			fc_varno = ((RangeTblRef *) fc_jtnode)->rtindex;

		fc_result->relids = bms_make_singleton(fc_varno);
	}
	else if (IsA(fc_jtnode, FromExpr))
	{
		FromExpr   *fc_f = (FromExpr *) fc_jtnode;
		ListCell   *fc_l;

		foreach(fc_l, fc_f->fromlist)
		{
			reduce_outer_joins_state *fc_sub_state;

			fc_sub_state = fc_reduce_outer_joins_pass1(lfirst(fc_l));
			fc_result->relids = bms_add_members(fc_result->relids,
											 fc_sub_state->relids);
			fc_result->contains_outer |= fc_sub_state->contains_outer;
			fc_result->sub_states = lappend(fc_result->sub_states, fc_sub_state);
		}
	}
	else if (IsA(fc_jtnode, JoinExpr))
	{
		JoinExpr   *fc_j = (JoinExpr *) fc_jtnode;
		reduce_outer_joins_state *fc_sub_state;

		/* 连接自身的 RT 索引不想在结果->relids 中 */
		if (IS_OUTER_JOIN(fc_j->jointype))
			fc_result->contains_outer = true;

		fc_sub_state = fc_reduce_outer_joins_pass1(fc_j->larg);
		fc_result->relids = bms_add_members(fc_result->relids,
										 fc_sub_state->relids);
		fc_result->contains_outer |= fc_sub_state->contains_outer;
		fc_result->sub_states = lappend(fc_result->sub_states, fc_sub_state);

		fc_sub_state = fc_reduce_outer_joins_pass1(fc_j->rarg);
		fc_result->relids = bms_add_members(fc_result->relids,
										 fc_sub_state->relids);
		fc_result->contains_outer |= fc_sub_state->contains_outer;
		fc_result->sub_states = lappend(fc_result->sub_states, fc_sub_state);
	}
	else
		elog(ERROR, "unrecognized node type: %d",
			 (int) nodeTag(fc_jtnode));
	return fc_result;
}

/*
 * reduce_outer_joins_pass2 - 第二阶段处理
 *
 *	jtnode：当前连接树节点
 *	state：阶段 1 收集的该节点的状态数据
 *	root：顶级规划状态
 *	nonnullable_rels：由于上层资格被强制非空的基础 relids 集
 *	nonnullable_vars：由于上层资格被强制非空的 Vars 列表
 *	forced_null_vars：由于上层资格被强制为空的 Vars 列表
 */
static void fc_reduce_outer_joins_pass2(Node *fc_jtnode,
						 reduce_outer_joins_state *fc_state,
						 PlannerInfo *fc_root,
						 Relids fc_nonnullable_rels,
						 List *fc_nonnullable_vars,
						 List *fc_forced_null_vars)
{
	/*
	 * 第二阶段不应下降到空子节点或基础 rel，
	 * 因为它仅在标记为 contains_outer 的子树上调用。
	 */
	if (fc_jtnode == NULL)
		elog(ERROR, "reached empty jointree");
	if (IsA(fc_jtnode, RangeTblRef))
		elog(ERROR, "reached base rel");
	else if (IsA(fc_jtnode, FromExpr))
	{
		FromExpr   *fc_f = (FromExpr *) fc_jtnode;
		ListCell   *fc_l;
		ListCell   *fc_s;
		Relids		fc_pass_nonnullable_rels;
		List	   *fc_pass_nonnullable_vars;
		List	   *fc_pass_forced_null_vars;

		/* 扫描资格以查看我们是否可以添加任何约束 */
		fc_pass_nonnullable_rels = find_nonnullable_rels(fc_f->quals);
		fc_pass_nonnullable_rels = bms_add_members(fc_pass_nonnullable_rels,
												fc_nonnullable_rels);
		fc_pass_nonnullable_vars = find_nonnullable_vars(fc_f->quals);
		fc_pass_nonnullable_vars = list_concat(fc_pass_nonnullable_vars,
											fc_nonnullable_vars);
		fc_pass_forced_null_vars = find_forced_null_vars(fc_f->quals);
		fc_pass_forced_null_vars = list_concat(fc_pass_forced_null_vars,
											fc_forced_null_vars);
		/* 递归——但仅进入有趣的子树 */
		Assert(list_length(fc_f->fromlist) == list_length(fc_state->sub_states));
		forboth(fc_l, fc_f->fromlist, fc_s, fc_state->sub_states)
		{
			reduce_outer_joins_state *fc_sub_state = lfirst(fc_s);

			if (fc_sub_state->contains_outer)
				fc_reduce_outer_joins_pass2(lfirst(fc_l), fc_sub_state, fc_root,
										 fc_pass_nonnullable_rels,
										 fc_pass_nonnullable_vars,
										 fc_pass_forced_null_vars);
		}
		bms_free(fc_pass_nonnullable_rels);
		/* 不幸的是，不能那么容易地清理 var 列表 */
	}
	else if (IsA(fc_jtnode, JoinExpr))
	{
		JoinExpr   *fc_j = (JoinExpr *) fc_jtnode;
		int			fc_rtindex = fc_j->rtindex;
		JoinType	fc_jointype = fc_j->jointype;
		reduce_outer_joins_state *fc_left_state = linitial(fc_state->sub_states);
		reduce_outer_joins_state *fc_right_state = lsecond(fc_state->sub_states);
		List	   *fc_local_nonnullable_vars = NIL;
		bool		fc_computed_local_nonnullable_vars = false;

		/* 我们可以简化这个连接吗？ */
		switch (fc_jointype)
		{
			case JOIN_INNER:
				break;
			case JOIN_LEFT:
				if (bms_overlap(fc_nonnullable_rels, fc_right_state->relids))
					fc_jointype = JOIN_INNER;
				break;
			case JOIN_RIGHT:
				if (bms_overlap(fc_nonnullable_rels, fc_left_state->relids))
					fc_jointype = JOIN_INNER;
				break;
			case JOIN_FULL:
				if (bms_overlap(fc_nonnullable_rels, fc_left_state->relids))
				{
					if (bms_overlap(fc_nonnullable_rels, fc_right_state->relids))
						fc_jointype = JOIN_INNER;
					else
						fc_jointype = JOIN_LEFT;
				}
				else
				{
					if (bms_overlap(fc_nonnullable_rels, fc_right_state->relids))
						fc_jointype = JOIN_RIGHT;
				}
				break;
			case JOIN_SEMI:
			case JOIN_ANTI:

				/*
				 * 这些只能是通过 pull_up_sublinks 引入的，
				 * 所以上层资格不可能引用它们的
				 * 右侧，并且没有检查的意义。
				 */
				break;
			default:
				elog(ERROR, "unrecognized join type: %d",
					 (int) fc_jointype);
				break;
		}

		/*
		 * 将 JOIN_RIGHT 转换为 JOIN_LEFT。注意，在我们
		 * 将 JOIN_FULL 减少到 JOIN_RIGHT 的情况下，这将意味着 JoinExpr 不再
		 * 与任何用于表示合并连接变量的 CoalesceExpr 的内部排序匹配。
		 * 目前我们不在乎，但要小心...
		 */
		if (fc_jointype == JOIN_RIGHT)
		{
			Node	   *fc_tmparg;

			fc_tmparg = fc_j->larg;
			fc_j->larg = fc_j->rarg;
			fc_j->rarg = fc_tmparg;
			fc_jointype = JOIN_LEFT;
			fc_right_state = linitial(fc_state->sub_states);
			fc_left_state = lsecond(fc_state->sub_states);
		}

		/*
		 * 查看我们是否可以将 JOIN_LEFT 减少为 JOIN_ANTI。
		 * 如果连接自身的资格对任何由于
		 * 上层资格级别被强制为空的变量是严格的，则是这样的情况。
		 * 注意：我们可以检测反连接的其他方法，
		 * 特别是如果我们检查来自右侧的 Vars 是否
		 * 必须由于表约束而非空。不过，这似乎太复杂和昂贵了（特别是，
		 * 需要小心较低的外连接）。目前看来这似乎足够了。
		 */
		if (fc_jointype == JOIN_LEFT)
		{
			List	   *fc_overlap;

			fc_local_nonnullable_vars = find_nonnullable_vars(fc_j->quals);
			fc_computed_local_nonnullable_vars = true;

			/*
			 * 仅检查 local_nonnullable_vars 和
			 * forced_null_vars 是否重叠是不够的：
			 * 我们需要知道重叠是否包括任何 RHS 变量。
			 */
			fc_overlap = list_intersection(fc_local_nonnullable_vars,
										fc_forced_null_vars);
			if (fc_overlap != NIL &&
				bms_overlap(pull_varnos(fc_root, (Node *) fc_overlap),
							fc_right_state->relids))
				fc_jointype = JOIN_ANTI;
		}

		/* 将连接类型更改（如有）应用于连接树节点和 RTE */
		if (fc_rtindex && fc_jointype != fc_j->jointype)
		{
			RangeTblEntry *fc_rte = rt_fetch(fc_rtindex, fc_root->parse->rtable);

			Assert(fc_rte->rtekind == RTE_JOIN);
			Assert(fc_rte->jointype == fc_j->jointype);
			fc_rte->jointype = fc_jointype;
		}
		fc_j->jointype = fc_jointype;

		/* 只有在此处还有更多要做的情况下才递归 */
		if (fc_left_state->contains_outer || fc_right_state->contains_outer)
		{
			Relids		fc_local_nonnullable_rels;
			List	   *fc_local_forced_null_vars;
			Relids		fc_pass_nonnullable_rels;
			List	   *fc_pass_nonnullable_vars;
			List	   *fc_pass_forced_null_vars;

			/*
			 * 如果这个连接（现在）是内部的，我们可以将其
			 * 资格提供的任何约束添加到我们从上面得到的约束中。
			 * 但是如果它是外部的，我们只能将本地约束
			 * 传递到可空侧，因为外连接从其
			 * 非可空侧永远不会消除任何行。此外，
			 * 将上层约束传递到可空侧没有意义，
			 * 因为如果有任何上层约束，我们就能够减少连接。
			 * （对于上层强制空约束，我们*必须不*将它们传递到可空侧——它们要么在这里适用，要么不适用。）
			 * 结果是，我们传递本地或上层约束中的任何一个，
			 * 从不将两者都传递给外连接的子节点。
			 *
			 * 请注意，SEMI 连接在这里的工作方式类似于内连接：
			 * 同时传递本地和上层约束是可以的。
			 * （不能有任何上层约束影响其内部侧，但
			 * 不值得为了避免传递它们而有单独的代码路径。）
			 *
			 * 在 FULL 连接中，我们只是放弃，不传递任何内容——
			 * 是否可能更聪明一点？
			 */
			if (fc_jointype != JOIN_FULL)
			{
				fc_local_nonnullable_rels = find_nonnullable_rels(fc_j->quals);
				if (!fc_computed_local_nonnullable_vars)
					fc_local_nonnullable_vars = find_nonnullable_vars(fc_j->quals);
				fc_local_forced_null_vars = find_forced_null_vars(fc_j->quals);
				if (fc_jointype == JOIN_INNER || fc_jointype == JOIN_SEMI)
				{
					/* 可以合并上层和本地约束 */
					fc_local_nonnullable_rels = bms_add_members(fc_local_nonnullable_rels,
															 fc_nonnullable_rels);
					fc_local_nonnullable_vars = list_concat(fc_local_nonnullable_vars,
														 fc_nonnullable_vars);
					fc_local_forced_null_vars = list_concat(fc_local_forced_null_vars,
														 fc_forced_null_vars);
				}
			}
			else
			{
				/* 计算这些没有用 */
				fc_local_nonnullable_rels = NULL;
				fc_local_forced_null_vars = NIL;
			}

			if (fc_left_state->contains_outer)
			{
				if (fc_jointype == JOIN_INNER || fc_jointype == JOIN_SEMI)
				{
					/* 传递本地和上层约束的并集 */
					fc_pass_nonnullable_rels = fc_local_nonnullable_rels;
					fc_pass_nonnullable_vars = fc_local_nonnullable_vars;
					fc_pass_forced_null_vars = fc_local_forced_null_vars;
				}
				else if (fc_jointype != JOIN_FULL) /* 即，LEFT 或 ANTI */
				{
					/* 不能将本地约束传递到非可空侧 */
					fc_pass_nonnullable_rels = fc_nonnullable_rels;
					fc_pass_nonnullable_vars = fc_nonnullable_vars;
					fc_pass_forced_null_vars = fc_forced_null_vars;
				}
				else
				{
					/* 在 JOIN_FULL 中没有约束传递 */
					fc_pass_nonnullable_rels = NULL;
					fc_pass_nonnullable_vars = NIL;
					fc_pass_forced_null_vars = NIL;
				}
				fc_reduce_outer_joins_pass2(fc_j->larg, fc_left_state, fc_root,
										 fc_pass_nonnullable_rels,
										 fc_pass_nonnullable_vars,
										 fc_pass_forced_null_vars);
			}

			if (fc_right_state->contains_outer)
			{
				if (fc_jointype != JOIN_FULL)	/* 即，INNER/LEFT/SEMI/ANTI */
				{
					/* 传递适当的约束，根据上述注释 */
					fc_pass_nonnullable_rels = fc_local_nonnullable_rels;
					fc_pass_nonnullable_vars = fc_local_nonnullable_vars;
					fc_pass_forced_null_vars = fc_local_forced_null_vars;
				}
				else
				{
					/* 在 JOIN_FULL 中没有约束传递 */
					fc_pass_nonnullable_rels = NULL;
					fc_pass_nonnullable_vars = NIL;
					fc_pass_forced_null_vars = NIL;
				}
				fc_reduce_outer_joins_pass2(fc_j->rarg, fc_right_state, fc_root,
										 fc_pass_nonnullable_rels,
										 fc_pass_nonnullable_vars,
										 fc_pass_forced_null_vars);
			}
			bms_free(fc_local_nonnullable_rels);
		}
	}
	else
		elog(ERROR, "unrecognized node type: %d",
			 (int) nodeTag(fc_jtnode));
}



/*
 * remove_useless_result_rtes
 *		尝试从连接树中移除 RTE_RESULT RTE。
 *
 * 我们可以通过知道 RTE_RESULT 返回恰好一行且没有输出列来从连接树中移除 RTE_RESULT 条目。因此，如果它与其他任何内容是内连接的，我们可以将其删除。对于某些外连接情况，也可能进行优化，具体如下。
 *
 * 这些优化中的一些依赖于识别 FromExprs 和 JoinExprs 的空（常量为真的）qual。这使得在表达式预处理后应用此优化工作是有用的，因为这将删除常量为真的 qual，从而允许识别更多的可优化情况。更重要的是，RTE_RESULT 存在的通常原因是我们提升了一个子查询或 VALUES 子句，因此很可能用常量替换了 Vars，使得 qual 更可能简化为常量真。此外，由于某些优化依赖于外连接类型，最好先执行 reduce_outer_joins()。
 *
 * 一个引用 RTE_RESULT RTE 的 PlaceHolderVar 对此过程构成了障碍：我们必须从 PHV 的 phrels 中移除 RTE_RESULT 的 relid，但我们不能将 phrels 集减至空。如果那样做了，而且 RTE_RESULT 是外连接的直接子项，我们就必须放弃，并且不移除 RTE_RESULT：没有其他地方可以评估 PlaceHolderVar。（也就是说，在这种情况下 RTE_RESULT 确实有输出列。）但是如果 RTE_RESULT 是内连接的直接子项，我们通常可以改变 PlaceHolderVar 的 phrels，以便在内连接处进行评估。这是可以的，因为我们真正关心的是 PHVs 在正确的外连接上方或下方被评估。然而，我们不能将 PHV 的评估推迟到其被使用之上；因此，下面有一些检查，看看输出 PHVs 是否在其他连接输入关系中被横向引用。
 *
 * 我们以前试图将这项工作作为 pull_up_subqueries() 的一部分进行，其中潜在可优化的案例被引入；但单独进行这样做要简单得多，也更有效。
 */
void remove_useless_result_rtes(PlannerInfo *fc_root)
{
	ListCell   *fc_cell;

	/* 联合树的顶层必须始终是 FromExpr */
	Assert(IsA(fc_root->parse->jointree, FromExpr));
	/* 递归 ... */
	fc_root->parse->jointree = (FromExpr *)
		fc_remove_useless_results_recurse(fc_root, (Node *) fc_root->parse->jointree);
	/* 我们仍然应该有一个 FromExpr */
	Assert(IsA(fc_root->parse->jointree, FromExpr));

	/*
	 * 移除任何引用 RTE_RESULT RTE 的 PlanRowMark。我们显然必须为刚移除的任何 RTE_RESULT 执行此操作。但是，对于未移除的 RTE 的 PlanRowMark 无论如何也可以去掉：由于该 RTE 只有一行可能的输出，因此没有必要让 EPQ 标记和恢复该行。
	 *
	 * 对于存活的 RTE_RESULT RTE，去除 PlanRowMark 是必要的，而非可选的；否则，我们将为 RTE_RESULT 生成一个整行 Var，而执行器对此没有支持。
	 */
	foreach(fc_cell, fc_root->rowMarks)
	{
		PlanRowMark *fc_rc = (PlanRowMark *) lfirst(fc_cell);

		if (rt_fetch(fc_rc->rti, fc_root->parse->rtable)->rtekind == RTE_RESULT)
			fc_root->rowMarks = foreach_delete_current(fc_root->rowMarks, fc_cell);
	}
}

/*
 * remove_useless_results_recurse
 *		递归处理 remove_useless_result_rtes 的核心部分。
 *
 * 这递归地处理联合树并返回修改后的联合树。
 */
static Node * fc_remove_useless_results_recurse(PlannerInfo *fc_root, Node *fc_jtnode)
{
	Assert(fc_jtnode != NULL);
	if (IsA(fc_jtnode, RangeTblRef))
	{
		/* 不能立即对 RangeTblRef 做任何操作 */
	}
	else if (IsA(fc_jtnode, FromExpr))
	{
		FromExpr   *fc_f = (FromExpr *) fc_jtnode;
		Relids		fc_result_relids = NULL;
		ListCell   *fc_cell;

		/*
		 * 只要至少有一个子节点，我们就可以从 fromlist 中删除 RTE_RESULT 关系，
		 * 因为连接到一行表不会改变任何东西。 （但是我们不能删除计算某些兄弟所需的 PHV(s) 的 RTE_RESULT。
		 * 下面的 Cleanup 转换会重新分配在连接时计算的 PHV，这对兄弟的使用来说已经太晚了。）
		 * 机制化这个规则的最简单方法是就地修改列表。
		 */
		foreach(fc_cell, fc_f->fromlist)
		{
			Node	   *fc_child = (Node *) lfirst(fc_cell);
			int			fc_varno;

			/* 递归转换子项... */
			fc_child = fc_remove_useless_results_recurse(fc_root, fc_child);
			/* ...并将其重新放回树中 */
			lfirst(fc_cell) = fc_child;

			/*
			 * 如果它是一个 RTE_RESULT，且至少有一个兄弟节点，并且没有兄弟节点引用依赖的 PHVs，我们可以将其丢弃。我们还不知道内连接的最终 relid 集会是什么，因此在该循环结束之前推迟清理 PHVs 等。
			 */
			if (list_length(fc_f->fromlist) > 1 &&
				(fc_varno = get_result_relid(fc_root, fc_child)) != 0 &&
				!fc_find_dependent_phvs_in_jointree(fc_root, (Node *) fc_f, fc_varno))
			{
				fc_f->fromlist = foreach_delete_current(fc_f->fromlist, fc_cell);
				fc_result_relids = bms_add_member(fc_result_relids, fc_varno);
			}
		}

		/*
		 * 如果我们丢弃了任何 RTE_RESULT RTE，则进行清理。如果存在多个，这样做会稍显低效，但优化支持代码以处理单 relid 情况似乎更好。
		 */
		if (fc_result_relids)
		{
			int			fc_varno = -1;

			while ((fc_varno = bms_next_member(fc_result_relids, fc_varno)) >= 0)
				fc_remove_result_refs(fc_root, fc_varno, (Node *) fc_f);
		}

		/*
		 * 如果我们不在 jointree 的顶部，将退化的 FromExpr 简化为其单个子项是合法的。（在顶部，我们必须保留 FromExpr，因为 Query.jointree 需要指向一个 FromExpr。）
		 */
		if (fc_f != fc_root->parse->jointree &&
			fc_f->quals == NULL &&
			list_length(fc_f->fromlist) == 1)
			return (Node *) linitial(fc_f->fromlist);
	}
	else if (IsA(fc_jtnode, JoinExpr))
	{
		JoinExpr   *fc_j = (JoinExpr *) fc_jtnode;
		int			fc_varno;

		/* 首先，递归 */
		fc_j->larg = fc_remove_useless_results_recurse(fc_root, fc_j->larg);
		fc_j->rarg = fc_remove_useless_results_recurse(fc_root, fc_j->rarg);

		/* 应用特定于连接类型的优化规则 */
		switch (fc_j->jointype)
		{
			case JOIN_INNER:

				/*
				 * 内连接等同于 FromExpr，因此如果任一侧简化为 RTE_RESULT rel，我们可以用仅包含另一侧的 FromExpr 替换连接；如果 qual 为空（JOIN ON TRUE），那么我们也可以省略 FromExpr。
				 *
				 * 就像 FromExpr 的情况一样，如果其他输入 rel 引用任何标记为在 RTE_RESULT rel 中进行评估的 PHVs，我们就无法简化，因为在这种情况下我们无法推迟其评估。但我们只需在其他输入在语法上合法地引用 RTE_RESULT rel 的情况下检查这一点。只有内连接和左连接的 RHS 允许有此类引用。
				 */
				if ((fc_varno = get_result_relid(fc_root, fc_j->larg)) != 0 &&
					!fc_find_dependent_phvs_in_jointree(fc_root, fc_j->rarg, fc_varno))
				{
					fc_remove_result_refs(fc_root, fc_varno, fc_j->rarg);
					if (fc_j->quals)
						fc_jtnode = (Node *)
							makeFromExpr(list_make1(fc_j->rarg), fc_j->quals);
					else
						fc_jtnode = fc_j->rarg;
				}
				else if ((fc_varno = get_result_relid(fc_root, fc_j->rarg)) != 0)
				{
					fc_remove_result_refs(fc_root, fc_varno, fc_j->larg);
					if (fc_j->quals)
						fc_jtnode = (Node *)
							makeFromExpr(list_make1(fc_j->larg), fc_j->quals);
					else
						fc_jtnode = fc_j->larg;
				}
				break;
			case JOIN_LEFT:

				/*
				 * 如果 RHS 是 RTE_RESULT，我们可以简化此情况，有两种不同的可能性：
				 *
				 * 如果 qual 为空（JOIN ON TRUE），那么连接可以简化为普通的内连接，因为每个 LHS 行必然具有一个连接伙伴。因此我们可以始终丢弃 RHS，正如上述 JOIN_INNER 情况中所做的那样。（同样，LHS 不得包含对 RHS 的侧向引用。）
				 *
				 * 否则，仍然可以认为每个 LHS 行应该被返回一次，并且由于 RHS 不返回任何列（除非需要在此进行评估的 PHVs），所以我们并不在意是否进行 null 扩展。因此在这种情况下，我们也可以忽略 qual 并丢弃左连接。
				 */
				if ((fc_varno = get_result_relid(fc_root, fc_j->rarg)) != 0 &&
					(fc_j->quals == NULL ||
					 !fc_find_dependent_phvs(fc_root, fc_varno)))
				{
					fc_remove_result_refs(fc_root, fc_varno, fc_j->larg);
					fc_jtnode = fc_j->larg;
				}
				break;
			case JOIN_SEMI:

				/*
				 * 如果 RHS 是 RTE_RESULT，我们可以简化此情况；连接 qual 实际上只是 LHS 的过滤资格，因为我们要么返回 LHS 行，要么不返回。为了简化，我们将过滤资格注入到新的 FromExpr 中。
				 *
				 * 有一个关于应该在 RHS 评估的 PHVs 的细节。这样的 PHVs 只能出现在半连接的 qual 中，因为查询的其余部分无法引用半连接 RHS 的任何输出。因此，在被检查之前，它们实际上不能变为 null，移除 PHV 包装是可以的。我们没有相关的基础设施，但 remove_result_refs() 将其重新标记为在 LHS 中进行评估，这样是可以的。
				 */
				if ((fc_varno = get_result_relid(fc_root, fc_j->rarg)) != 0)
				{
					fc_remove_result_refs(fc_root, fc_varno, fc_j->larg);
					if (fc_j->quals)
						fc_jtnode = (Node *)
							makeFromExpr(list_make1(fc_j->larg), fc_j->quals);
					else
						fc_jtnode = fc_j->larg;
				}
				break;
			case JOIN_FULL:
			case JOIN_ANTI:
				/* 对于这些情况我们没有特别的聪明才智 */
				break;
			default:
				/* 注意：此时 JOIN_RIGHT 应该已消失 */
				elog(ERROR, "unrecognized join type: %d",
					 (int) fc_j->jointype);
				break;
		}
	}
	else
		elog(ERROR, "unrecognized node type: %d",
			 (int) nodeTag(fc_jtnode));
	return fc_jtnode;
}

/*
 * get_result_relid
 *		如果 jtnode 是 RTE_RESULT RTE 的 RangeTblRef，返回其 relid；否则返回 0。
 */
static int get_result_relid(PlannerInfo *fc_root, Node *fc_jtnode)
{
	int			fc_varno;

	if (!IsA(fc_jtnode, RangeTblRef))
		return 0;
	fc_varno = ((RangeTblRef *) fc_jtnode)->rtindex;
	if (rt_fetch(fc_varno, fc_root->parse->rtable)->rtekind != RTE_RESULT)
		return 0;
	return fc_varno;
}

/*
 * remove_result_refs
 *		用于丢弃不必要的 RTE_RESULT RTE 的帮助例程。
 *
 * 这并不会物理地从 jointree 中移除 RTE，因为在 remove_useless_results_recurse 中更容易处理。这所做的是对树的其余部分进行必要的清理：我们必须调整可能引用 RTE 的任何 PHVs。确保在 jointree 是有效（没有断开节点）的情况下调用此函数。
 *
 * 注意，我们不需要处理 append_rel_list，因为直接在 jointree 中引用的 RTE 不会成为 appendrel 成员。
 *
 * varno 是 RTE_RESULT 的 relid。
 * newjtloc 是任何引用 RTE_RESULT 的 PHVs 应该在其处进行评估的 jointree 位置。
 */
static void fc_remove_result_refs(PlannerInfo *fc_root, int fc_varno, Node *fc_newjtloc)
{
	/* 根据需要修复 PlaceHolderVars */
	/* 如果哪里没有 PHVs，我们可以跳过这一部分 */
	if (fc_root->glob->lastPHId != 0)
	{
		Relids		fc_subrelids;

		fc_subrelids = get_relids_in_jointree(fc_newjtloc, false);
		Assert(!bms_is_empty(fc_subrelids));
		fc_substitute_phv_relids((Node *) fc_root->parse, fc_varno, fc_subrelids);
		fc_fix_append_rel_relids(fc_root->append_rel_list, fc_varno, fc_subrelids);
	}

	/*
	 * 我们还需要移除任何引用 RTE 的 PlanRowMark，但我们将推迟此工作，直到返回 remove_useless_result_rtes。
	 */
}



/*
 * find_dependent_phvs - 是否存在与给定的 varno 完全相同的 PlaceHolderVars？
 *
 * 当我们想要查看查询中是否有这样的 PHVs 时，应使用 find_dependent_phvs。 另一个用例是查看连接树的子树是否包含这样的 PHVs；但是为此，我们不仅需要查看连接树节点本身，还需要查看引用的 RTEs。 为此，请使用 find_dependent_phvs_in_jointree。
 */

typedef struct
{
	Relids		relids;
	int			sublevels_up;
} find_dependent_phvs_context;

static bool fc_find_dependent_phvs_walker(Node *fc_node,
						   find_dependent_phvs_context *fc_context)
{
	if (fc_node == NULL)
		return false;
	if (IsA(fc_node, PlaceHolderVar))
	{
		PlaceHolderVar *fc_phv = (PlaceHolderVar *) fc_node;

		if (fc_phv->phlevelsup == fc_context->sublevels_up &&
			bms_equal(fc_context->relids, fc_phv->phrels))
			return true;
		/* 继续检查子节点 */
	}
	if (IsA(fc_node, Query))
	{
		/* 递归进入子选择 */
		bool		fc_result;

		fc_context->sublevels_up++;
		fc_result = query_tree_walker((Query *) fc_node,
								   fc_find_dependent_phvs_walker,
								   (void *) fc_context, 0);
		fc_context->sublevels_up--;
		return fc_result;
	}
	/* 这里不需要处理规划辅助节点 */
	Assert(!IsA(fc_node, SpecialJoinInfo));
	Assert(!IsA(fc_node, AppendRelInfo));
	Assert(!IsA(fc_node, PlaceHolderInfo));
	Assert(!IsA(fc_node, MinMaxAggInfo));

	return expression_tree_walker(fc_node, fc_find_dependent_phvs_walker,
								  (void *) fc_context);
}

static bool fc_find_dependent_phvs(PlannerInfo *fc_root, int fc_varno)
{
	find_dependent_phvs_context fc_context;

	/* 如果哪里都没有 PHVs，我们不需要费太多力气 */
	if (fc_root->glob->lastPHId == 0)
		return false;

	fc_context.relids = bms_make_singleton(fc_varno);
	fc_context.sublevels_up = 0;

	return query_tree_walker(fc_root->parse,
							 fc_find_dependent_phvs_walker,
							 (void *) &fc_context,
							 0);
}

static bool fc_find_dependent_phvs_in_jointree(PlannerInfo *fc_root, Node *fc_node, int fc_varno)
{
	find_dependent_phvs_context fc_context;
	Relids		fc_subrelids;
	int			fc_relid;

	/* 如果哪里都没有 PHVs，我们不需要费太多力气 */
	if (fc_root->glob->lastPHId == 0)
		return false;

	fc_context.relids = bms_make_singleton(fc_varno);
	fc_context.sublevels_up = 0;

	/*
	 * 查看连接树片段本身是否包含引用（在连接条件中）
	 */
	if (fc_find_dependent_phvs_walker(fc_node, &fc_context))
		return true;

	/*
	 * 否则，识别被引用的 RTEs 集（我们可以忽略连接，
	 * 因为它们应该已经被扁平化，所以它们的连接别名列表已经不再重要），并且逐个检查每个 RTE。我们可以忽略未标记为 LATERAL 的 RTEs，因为它们不可能包含对其他 RTEs 的交叉引用。
	 */
	fc_subrelids = get_relids_in_jointree(fc_node, false);
	fc_relid = -1;
	while ((fc_relid = bms_next_member(fc_subrelids, fc_relid)) >= 0)
	{
		RangeTblEntry *fc_rte = rt_fetch(fc_relid, fc_root->parse->rtable);

		if (fc_rte->lateral &&
			range_table_entry_walker(fc_rte,
									 fc_find_dependent_phvs_walker,
									 (void *) &fc_context,
									 0))
			return true;
	}

	return false;
}

/*
 * substitute_phv_relids - 在提升子查询或移除 RTE_RESULT 连接树项后调整 PlaceHolderVar relid 集
 *
 * 找到给定树中引用了提升的 relid 的任何 PlaceHolderVar 节点，并将它们更改为引用替换后的 relid(s)。
 *
 * 注意：尽管这有一个 walker 的形式，但我们在原地修改节点，这应该是可以的，因为树在之前的 pullup_replace_vars 中已被复制。 然而，避免修改位图集的原始值，因为 expression_tree_mutator 不会复制它们。
 */

typedef struct
{
	int			varno;
	int			sublevels_up;
	Relids		subrelids;
} substitute_phv_relids_context;

static bool fc_substitute_phv_relids_walker(Node *fc_node,
							 substitute_phv_relids_context *fc_context)
{
	if (fc_node == NULL)
		return false;
	if (IsA(fc_node, PlaceHolderVar))
	{
		PlaceHolderVar *fc_phv = (PlaceHolderVar *) fc_node;

		if (fc_phv->phlevelsup == fc_context->sublevels_up &&
			bms_is_member(fc_context->varno, fc_phv->phrels))
		{
			fc_phv->phrels = bms_union(fc_phv->phrels,
									fc_context->subrelids);
			fc_phv->phrels = bms_del_member(fc_phv->phrels,
										 fc_context->varno);
			/* 确保我们没有破坏 PHV */
			Assert(!bms_is_empty(fc_phv->phrels));
		}
		/* 继续检查子节点 */
	}
	if (IsA(fc_node, Query))
	{
		/* 递归进入子选择 */
		bool		fc_result;

		fc_context->sublevels_up++;
		fc_result = query_tree_walker((Query *) fc_node,
								   fc_substitute_phv_relids_walker,
								   (void *) fc_context, 0);
		fc_context->sublevels_up--;
		return fc_result;
	}
	/* 这里不需要处理规划辅助节点 */
	Assert(!IsA(fc_node, SpecialJoinInfo));
	Assert(!IsA(fc_node, AppendRelInfo));
	Assert(!IsA(fc_node, PlaceHolderInfo));
	Assert(!IsA(fc_node, MinMaxAggInfo));

	return expression_tree_walker(fc_node, fc_substitute_phv_relids_walker,
								  (void *) fc_context);
}

static void fc_substitute_phv_relids(Node *fc_node, int fc_varno, Relids fc_subrelids)
{
	substitute_phv_relids_context fc_context;

	fc_context.varno = fc_varno;
	fc_context.sublevels_up = 0;
	fc_context.subrelids = fc_subrelids;

	/*
	 * 必须准备好以查询或裸表达式树开始。
	 */
	query_or_expression_tree_walker(fc_node,
									fc_substitute_phv_relids_walker,
									(void *) &fc_context,
									0);
}

/*
 * fix_append_rel_relids: 更新 AppendRelInfo 节点的 RT-index 字段
 *
 * 当我们提升子查询时，任何对子查询的 RT 索引的 AppendRelInfo 引用都必须替换为被替代的 relid（并且最好只有一个）。我们还需要对它们的 translated_vars 列表应用 substitute_phv_relids，因为那些可能包含 PlaceHolderVars。
 *
 * 我们假定我们可以在原地修改 AppendRelInfo 节点。
 */
static void fc_fix_append_rel_relids(List *fc_append_rel_list, int fc_varno, Relids fc_subrelids)
{
	ListCell   *fc_l;
	int			fc_subvarno = -1;

	/*
	 * 我们只想一次提取成员 relid，但如果有多个成员，我们不能立即失败；可能所有的 AppendRelInfo 节点都没有引用它。因此，在第一次使用时计算它。注意，如果集合不是单例，bms_singleton_member 会发出警告。
	 */
	foreach(fc_l, fc_append_rel_list)
	{
		AppendRelInfo *fc_appinfo = (AppendRelInfo *) lfirst(fc_l);

		/* parent_relid 不应该是提升目标 */
		Assert(fc_appinfo->parent_relid != fc_varno);

		if (fc_appinfo->child_relid == fc_varno)
		{
			if (fc_subvarno < 0)
				fc_subvarno = bms_singleton_member(fc_subrelids);
			fc_appinfo->child_relid = fc_subvarno;
		}

		/* 还要修复其翻译变量中的任何 PHVs */
		fc_substitute_phv_relids((Node *) fc_appinfo->translated_vars,
							  fc_varno, fc_subrelids);
	}
}

/*
 * get_relids_in_jointree: 获取联合树中存在的 RT 索引集合
 *
 * 如果 include_joins 为真，连接 RT 索引将被包括；如果为假，
 * 仅包括基本关系。
 */
Relids get_relids_in_jointree(Node *fc_jtnode, bool fc_include_joins)
{
	Relids		fc_result = NULL;

	if (fc_jtnode == NULL)
		return fc_result;
	if (IsA(fc_jtnode, RangeTblRef))
	{
		int			fc_varno = ((RangeTblRef *) fc_jtnode)->rtindex;

		fc_result = bms_make_singleton(fc_varno);
	}
	else if (IsA(fc_jtnode, FromExpr))
	{
		FromExpr   *fc_f = (FromExpr *) fc_jtnode;
		ListCell   *fc_l;

		foreach(fc_l, fc_f->fromlist)
		{
			fc_result = bms_join(fc_result,
							  get_relids_in_jointree(lfirst(fc_l),
													 fc_include_joins));
		}
	}
	else if (IsA(fc_jtnode, JoinExpr))
	{
		JoinExpr   *fc_j = (JoinExpr *) fc_jtnode;

		fc_result = get_relids_in_jointree(fc_j->larg, fc_include_joins);
		fc_result = bms_join(fc_result,
						  get_relids_in_jointree(fc_j->rarg, fc_include_joins));
		if (fc_include_joins && fc_j->rtindex)
			fc_result = bms_add_member(fc_result, fc_j->rtindex);
	}
	else
		elog(ERROR, "unrecognized node type: %d",
			 (int) nodeTag(fc_jtnode));
	return fc_result;
}

/*
 * get_relids_for_join: 获取构成连接的基本 RT 索引集合
 */
Relids get_relids_for_join(Query *fc_query, int fc_joinrelid)
{
	Node	   *fc_jtnode;

	fc_jtnode = fc_find_jointree_node_for_rel((Node *) fc_query->jointree,
										fc_joinrelid);
	if (!fc_jtnode)
		elog(ERROR, "could not find join node %d", fc_joinrelid);
	return get_relids_in_jointree(fc_jtnode, false);
}

/*
 * find_jointree_node_for_rel: 定位基本或连接 RT 索引的联合树节点
 *
 * 如果未找到，则返回 NULL
 */
static Node * fc_find_jointree_node_for_rel(Node *fc_jtnode, int fc_relid)
{
	if (fc_jtnode == NULL)
		return NULL;
	if (IsA(fc_jtnode, RangeTblRef))
	{
		int			fc_varno = ((RangeTblRef *) fc_jtnode)->rtindex;

		if (fc_relid == fc_varno)
			return fc_jtnode;
	}
	else if (IsA(fc_jtnode, FromExpr))
	{
		FromExpr   *fc_f = (FromExpr *) fc_jtnode;
		ListCell   *fc_l;

		foreach(fc_l, fc_f->fromlist)
		{
			fc_jtnode = fc_find_jointree_node_for_rel(lfirst(fc_l), fc_relid);
			if (fc_jtnode)
				return fc_jtnode;
		}
	}
	else if (IsA(fc_jtnode, JoinExpr))
	{
		JoinExpr   *fc_j = (JoinExpr *) fc_jtnode;

		if (fc_relid == fc_j->rtindex)
			return fc_jtnode;
		fc_jtnode = fc_find_jointree_node_for_rel(fc_j->larg, fc_relid);
		if (fc_jtnode)
			return fc_jtnode;
		fc_jtnode = fc_find_jointree_node_for_rel(fc_j->rarg, fc_relid);
		if (fc_jtnode)
			return fc_jtnode;
	}
	else
		elog(ERROR, "unrecognized node type: %d",
			 (int) nodeTag(fc_jtnode));
	return NULL;
}
